Last active
April 16, 2026 14:05
-
-
Save shaal/51dbafb8a7af28274ab2e77f910aef3a to your computer and use it in GitHub Desktop.
Miller-Rabin demo (quick prime finder)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Miller-Rabin Primality Test</title> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;600;700&family=Space+Grotesk:wght@400;600;700&display=swap'); | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| :root { | |
| --bg: #0a0a0f; | |
| --surface: #12121a; | |
| --border: #1e1e2e; | |
| --text: #e0e0e8; | |
| --dim: #6a6a80; | |
| --accent: #7c6aff; | |
| --accent-glow: #7c6aff66; | |
| --green: #3dffa0; | |
| --green-glow: #3dffa044; | |
| --red: #ff4a6a; | |
| --red-glow: #ff4a6a44; | |
| --amber: #ffb347; | |
| --amber-glow: #ffb34744; | |
| --cyan: #4af0ff; | |
| --cyan-glow: #4af0ff33; | |
| } | |
| body { | |
| background: var(--bg); | |
| color: var(--text); | |
| font-family: 'Space Grotesk', sans-serif; | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| /* Starfield background */ | |
| body::before { | |
| content: ''; | |
| position: fixed; | |
| inset: 0; | |
| background: | |
| radial-gradient(1px 1px at 10% 20%, rgba(124,106,255,0.3), transparent), | |
| radial-gradient(1px 1px at 30% 60%, rgba(61,255,160,0.2), transparent), | |
| radial-gradient(1px 1px at 50% 10%, rgba(74,240,255,0.3), transparent), | |
| radial-gradient(1px 1px at 70% 80%, rgba(255,179,71,0.2), transparent), | |
| radial-gradient(1px 1px at 90% 40%, rgba(124,106,255,0.3), transparent), | |
| radial-gradient(1px 1px at 15% 85%, rgba(61,255,160,0.2), transparent), | |
| radial-gradient(1px 1px at 80% 15%, rgba(74,240,255,0.2), transparent), | |
| radial-gradient(1px 1px at 45% 45%, rgba(255,74,106,0.2), transparent), | |
| radial-gradient(1px 1px at 60% 90%, rgba(124,106,255,0.2), transparent), | |
| radial-gradient(1px 1px at 25% 35%, rgba(255,179,71,0.15), transparent); | |
| pointer-events: none; | |
| z-index: 0; | |
| } | |
| .container { | |
| max-width: 960px; | |
| margin: 0 auto; | |
| padding: 3rem 1.5rem; | |
| position: relative; | |
| z-index: 1; | |
| } | |
| header { | |
| text-align: center; | |
| margin-bottom: 3rem; | |
| } | |
| h1 { | |
| font-size: 2.6rem; | |
| font-weight: 700; | |
| letter-spacing: -0.02em; | |
| background: linear-gradient(135deg, var(--accent), var(--cyan), var(--green)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| margin-bottom: 0.5rem; | |
| } | |
| header p { | |
| color: var(--dim); | |
| font-size: 1.05rem; | |
| max-width: 520px; | |
| margin: 0 auto; | |
| line-height: 1.6; | |
| } | |
| /* Input section */ | |
| .input-section { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 16px; | |
| padding: 2rem; | |
| margin-bottom: 2rem; | |
| } | |
| .input-section label { | |
| display: block; | |
| font-size: 0.85rem; | |
| font-weight: 600; | |
| color: var(--dim); | |
| text-transform: uppercase; | |
| letter-spacing: 0.08em; | |
| margin-bottom: 0.6rem; | |
| } | |
| .input-row { | |
| display: flex; | |
| gap: 1rem; | |
| align-items: stretch; | |
| } | |
| .input-row input { | |
| flex: 1; | |
| background: var(--bg); | |
| border: 1px solid var(--border); | |
| border-radius: 10px; | |
| padding: 0.9rem 1.2rem; | |
| color: var(--text); | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 1rem; | |
| outline: none; | |
| transition: border-color 0.2s; | |
| } | |
| .input-row input:focus { | |
| border-color: var(--accent); | |
| box-shadow: 0 0 0 3px var(--accent-glow); | |
| } | |
| .input-row input::placeholder { color: #333348; } | |
| .btn { | |
| padding: 0.9rem 1.8rem; | |
| border: none; | |
| border-radius: 10px; | |
| font-family: 'Space Grotesk', sans-serif; | |
| font-weight: 600; | |
| font-size: 0.95rem; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| white-space: nowrap; | |
| } | |
| .btn-primary { | |
| background: var(--accent); | |
| color: #fff; | |
| } | |
| .btn-primary:hover { | |
| box-shadow: 0 0 20px var(--accent-glow), 0 4px 15px rgba(0,0,0,0.3); | |
| transform: translateY(-1px); | |
| } | |
| .btn-primary:disabled { | |
| opacity: 0.4; | |
| cursor: not-allowed; | |
| transform: none; | |
| box-shadow: none; | |
| } | |
| .presets { | |
| display: flex; | |
| gap: 0.5rem; | |
| margin-top: 1rem; | |
| flex-wrap: wrap; | |
| } | |
| .preset-btn { | |
| background: var(--bg); | |
| border: 1px solid var(--border); | |
| color: var(--dim); | |
| padding: 0.4rem 0.9rem; | |
| border-radius: 8px; | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.75rem; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .preset-btn:hover { | |
| border-color: var(--accent); | |
| color: var(--text); | |
| } | |
| /* Decomposition */ | |
| .decomposition { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 16px; | |
| padding: 2rem; | |
| margin-bottom: 2rem; | |
| display: none; | |
| } | |
| .decomposition.visible { display: block; animation: fadeSlideIn 0.4s ease; } | |
| .section-title { | |
| font-size: 0.8rem; | |
| font-weight: 600; | |
| color: var(--dim); | |
| text-transform: uppercase; | |
| letter-spacing: 0.08em; | |
| margin-bottom: 1.2rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .section-title .step-num { | |
| background: var(--accent); | |
| color: #fff; | |
| width: 22px; | |
| height: 22px; | |
| border-radius: 6px; | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 0.7rem; | |
| } | |
| .math-display { | |
| font-family: 'JetBrains Mono', monospace; | |
| background: var(--bg); | |
| border: 1px solid var(--border); | |
| border-radius: 10px; | |
| padding: 1.2rem 1.5rem; | |
| font-size: 0.9rem; | |
| line-height: 1.8; | |
| overflow-x: auto; | |
| } | |
| .math-display .label { | |
| color: var(--dim); | |
| font-size: 0.75rem; | |
| } | |
| .math-display .value { | |
| color: var(--cyan); | |
| word-break: break-all; | |
| } | |
| .math-display .operator { color: var(--dim); } | |
| .math-display .highlight-s { color: var(--amber); } | |
| .math-display .highlight-d { color: var(--green); } | |
| .big-number { | |
| color: var(--cyan); | |
| word-break: break-all; | |
| line-height: 1.6; | |
| position: relative; | |
| } | |
| .big-number .digit-group { | |
| display: inline; | |
| transition: opacity 0.3s; | |
| } | |
| .big-number .digit-group:nth-child(even) { color: #3ac8e8; } | |
| .big-number .digit-group:nth-child(odd) { color: var(--cyan); } | |
| .digit-count { | |
| color: var(--dim); | |
| font-size: 0.75rem; | |
| margin-top: 0.4rem; | |
| } | |
| /* Witness rounds */ | |
| .rounds-container { | |
| display: none; | |
| } | |
| .rounds-container.visible { display: block; } | |
| .witness-round { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 16px; | |
| margin-bottom: 1.5rem; | |
| overflow: hidden; | |
| animation: fadeSlideIn 0.5s ease; | |
| } | |
| .round-header { | |
| padding: 1.2rem 1.5rem; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| cursor: pointer; | |
| user-select: none; | |
| transition: background 0.2s; | |
| } | |
| .round-header:hover { background: #16162080; } | |
| .round-header-left { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| } | |
| .round-number { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| color: var(--dim); | |
| background: var(--bg); | |
| border: 1px solid var(--border); | |
| border-radius: 8px; | |
| padding: 0.35rem 0.65rem; | |
| min-width: 60px; | |
| text-align: center; | |
| } | |
| .witness-value { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.85rem; | |
| color: var(--text); | |
| } | |
| .witness-value span { color: var(--accent); } | |
| .round-verdict { | |
| font-size: 0.8rem; | |
| font-weight: 600; | |
| padding: 0.3rem 0.8rem; | |
| border-radius: 20px; | |
| } | |
| .verdict-pass { | |
| background: var(--green-glow); | |
| color: var(--green); | |
| border: 1px solid var(--green); | |
| } | |
| .verdict-fail { | |
| background: var(--red-glow); | |
| color: var(--red); | |
| border: 1px solid var(--red); | |
| } | |
| .round-body { | |
| border-top: 1px solid var(--border); | |
| padding: 1.5rem; | |
| display: none; | |
| } | |
| .round-body.open { display: block; } | |
| /* Steps pipeline */ | |
| .step-pipeline { | |
| position: relative; | |
| padding-left: 1.5rem; | |
| } | |
| .step-pipeline::before { | |
| content: ''; | |
| position: absolute; | |
| left: 5px; | |
| top: 8px; | |
| bottom: 8px; | |
| width: 2px; | |
| background: var(--border); | |
| } | |
| .pipeline-step { | |
| position: relative; | |
| margin-bottom: 1rem; | |
| padding-left: 1.2rem; | |
| animation: fadeSlideIn 0.3s ease; | |
| } | |
| .pipeline-step:last-child { margin-bottom: 0; } | |
| .pipeline-step::before { | |
| content: ''; | |
| position: absolute; | |
| left: -1.5rem; | |
| top: 8px; | |
| width: 12px; | |
| height: 12px; | |
| border-radius: 50%; | |
| background: var(--border); | |
| border: 2px solid var(--bg); | |
| } | |
| .pipeline-step.step-active::before { background: var(--amber); box-shadow: 0 0 8px var(--amber-glow); } | |
| .pipeline-step.step-pass::before { background: var(--green); box-shadow: 0 0 8px var(--green-glow); } | |
| .pipeline-step.step-fail::before { background: var(--red); box-shadow: 0 0 8px var(--red-glow); } | |
| .pipeline-step.step-neutral::before { background: var(--dim); } | |
| .step-label { | |
| font-size: 0.78rem; | |
| font-weight: 600; | |
| color: var(--dim); | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| margin-bottom: 0.3rem; | |
| } | |
| .step-math { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.82rem; | |
| color: var(--text); | |
| word-break: break-all; | |
| line-height: 1.6; | |
| } | |
| .step-math .eq { color: var(--dim); } | |
| .step-math .val { color: var(--cyan); } | |
| .step-math .cond { color: var(--amber); font-style: italic; } | |
| .step-math .yes { color: var(--green); } | |
| .step-math .no { color: var(--red); } | |
| /* Final result */ | |
| .final-result { | |
| text-align: center; | |
| padding: 2.5rem 2rem; | |
| border-radius: 16px; | |
| margin-top: 2rem; | |
| display: none; | |
| animation: fadeSlideIn 0.5s ease; | |
| } | |
| .final-result.visible { display: block; } | |
| .final-result.is-prime { | |
| background: linear-gradient(135deg, #0d2218, #0a1a20); | |
| border: 1px solid var(--green); | |
| box-shadow: 0 0 40px var(--green-glow), inset 0 0 40px var(--green-glow); | |
| } | |
| .final-result.is-composite { | |
| background: linear-gradient(135deg, #220d15, #1a0a0e); | |
| border: 1px solid var(--red); | |
| box-shadow: 0 0 40px var(--red-glow), inset 0 0 40px var(--red-glow); | |
| } | |
| .result-icon { | |
| font-size: 3rem; | |
| margin-bottom: 0.8rem; | |
| } | |
| .result-text { | |
| font-size: 1.6rem; | |
| font-weight: 700; | |
| margin-bottom: 0.4rem; | |
| } | |
| .is-prime .result-text { color: var(--green); } | |
| .is-composite .result-text { color: var(--red); } | |
| .result-sub { | |
| color: var(--dim); | |
| font-size: 0.9rem; | |
| max-width: 440px; | |
| margin: 0 auto; | |
| line-height: 1.5; | |
| } | |
| .confidence { | |
| margin-top: 1.2rem; | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.8rem; | |
| } | |
| .confidence-bar { | |
| width: 200px; | |
| height: 6px; | |
| background: var(--border); | |
| border-radius: 3px; | |
| margin: 0.5rem auto 0; | |
| overflow: hidden; | |
| } | |
| .confidence-fill { | |
| height: 100%; | |
| background: var(--green); | |
| border-radius: 3px; | |
| transition: width 0.6s ease; | |
| } | |
| /* Running indicator */ | |
| .running-indicator { | |
| display: none; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 0.6rem; | |
| padding: 1rem; | |
| color: var(--amber); | |
| font-size: 0.9rem; | |
| } | |
| .running-indicator.visible { display: flex; } | |
| .spinner { | |
| width: 18px; | |
| height: 18px; | |
| border: 2px solid var(--border); | |
| border-top-color: var(--amber); | |
| border-radius: 50%; | |
| animation: spin 0.8s linear infinite; | |
| } | |
| @keyframes spin { to { transform: rotate(360deg); } } | |
| @keyframes fadeSlideIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| /* Squaring grid */ | |
| .squaring-grid { | |
| display: grid; | |
| grid-template-columns: auto 1fr auto; | |
| gap: 0.2rem 1rem; | |
| align-items: baseline; | |
| margin-top: 0.5rem; | |
| } | |
| .sq-iter { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.72rem; | |
| color: var(--dim); | |
| } | |
| .sq-val { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.78rem; | |
| color: var(--text); | |
| word-break: break-all; | |
| } | |
| .sq-check { | |
| font-size: 0.78rem; | |
| font-weight: 600; | |
| } | |
| .sq-check.hit { color: var(--green); } | |
| .sq-check.miss { color: var(--dim); } | |
| /* ── Pi Hunt Section ─────────────────────── */ | |
| .pi-divider { | |
| text-align: center; | |
| margin: 4rem 0 3rem; | |
| position: relative; | |
| } | |
| .pi-divider::before { | |
| content: ''; | |
| position: absolute; | |
| left: 0; right: 0; top: 50%; | |
| height: 1px; | |
| background: linear-gradient(90deg, transparent, var(--border), transparent); | |
| } | |
| .pi-divider-label { | |
| position: relative; | |
| background: var(--bg); | |
| padding: 0 1.5rem; | |
| color: var(--dim); | |
| font-size: 0.8rem; | |
| text-transform: uppercase; | |
| letter-spacing: 0.1em; | |
| } | |
| .pi-header { | |
| text-align: center; | |
| margin-bottom: 2rem; | |
| } | |
| .pi-symbol { | |
| font-size: 5rem; | |
| font-weight: 700; | |
| background: linear-gradient(135deg, var(--amber), #ff6b6b, var(--accent)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| line-height: 1; | |
| margin-bottom: 0.5rem; | |
| filter: drop-shadow(0 0 30px var(--amber-glow)); | |
| } | |
| .pi-header h2 { | |
| font-size: 1.6rem; | |
| font-weight: 700; | |
| color: var(--text); | |
| margin-bottom: 0.4rem; | |
| } | |
| .pi-header p { | |
| color: var(--dim); | |
| font-size: 0.95rem; | |
| max-width: 560px; | |
| margin: 0 auto; | |
| line-height: 1.6; | |
| } | |
| /* Pi digit display */ | |
| .pi-digit-display { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 16px; | |
| padding: 1.5rem; | |
| margin-bottom: 1.5rem; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .pi-digit-display .section-title { margin-bottom: 0.8rem; } | |
| .pi-digits-scroll { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.82rem; | |
| line-height: 1.9; | |
| max-height: 140px; | |
| overflow-y: auto; | |
| word-break: break-all; | |
| padding: 0.8rem; | |
| background: var(--bg); | |
| border-radius: 10px; | |
| border: 1px solid var(--border); | |
| } | |
| .pi-digits-scroll .d-consumed { color: var(--dim); opacity: 0.4; } | |
| .pi-digits-scroll .d-current { color: var(--amber); font-weight: 700; text-decoration: underline; text-underline-offset: 3px; } | |
| .pi-digits-scroll .d-upcoming { color: #333348; } | |
| .pi-digits-scroll .d-prime { color: var(--green); font-weight: 700; } | |
| .pi-digits-scroll .d-decimal { color: var(--dim); font-weight: 400; } | |
| /* Controls */ | |
| .pi-controls { | |
| display: flex; | |
| gap: 0.8rem; | |
| align-items: center; | |
| margin-bottom: 1.5rem; | |
| flex-wrap: wrap; | |
| } | |
| .pi-controls .btn { font-size: 0.85rem; padding: 0.7rem 1.4rem; } | |
| .btn-stop { | |
| background: var(--red); | |
| color: #fff; | |
| } | |
| .btn-stop:hover { | |
| box-shadow: 0 0 20px var(--red-glow); | |
| } | |
| .speed-control { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| margin-left: auto; | |
| font-size: 0.8rem; | |
| color: var(--dim); | |
| } | |
| .speed-control select { | |
| background: var(--bg); | |
| border: 1px solid var(--border); | |
| border-radius: 8px; | |
| color: var(--text); | |
| padding: 0.4rem 0.6rem; | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.78rem; | |
| outline: none; | |
| cursor: pointer; | |
| } | |
| .pi-stats { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); | |
| gap: 0.8rem; | |
| margin-bottom: 1.5rem; | |
| } | |
| .pi-stat { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| padding: 1rem 1.2rem; | |
| text-align: center; | |
| } | |
| .pi-stat-value { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 1.6rem; | |
| font-weight: 700; | |
| color: var(--text); | |
| } | |
| .pi-stat-label { | |
| font-size: 0.72rem; | |
| color: var(--dim); | |
| text-transform: uppercase; | |
| letter-spacing: 0.06em; | |
| margin-top: 0.2rem; | |
| } | |
| .pi-stat.stat-primes .pi-stat-value { color: var(--green); } | |
| .pi-stat.stat-composites .pi-stat-value { color: var(--red); } | |
| .pi-stat.stat-skipped .pi-stat-value { color: var(--dim); } | |
| .pi-stat.stat-position .pi-stat-value { color: var(--amber); } | |
| /* Results log */ | |
| .pi-results-log { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 16px; | |
| overflow: hidden; | |
| } | |
| .pi-results-log .section-title { | |
| padding: 1.2rem 1.5rem 0; | |
| margin-bottom: 0; | |
| } | |
| .pi-log-list { | |
| max-height: 500px; | |
| overflow-y: auto; | |
| padding: 0.8rem; | |
| } | |
| .pi-log-entry { | |
| display: grid; | |
| grid-template-columns: 55px 1fr auto; | |
| gap: 0.8rem; | |
| align-items: center; | |
| padding: 0.7rem 0.8rem; | |
| border-radius: 10px; | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.78rem; | |
| animation: fadeSlideIn 0.3s ease; | |
| transition: background 0.2s; | |
| } | |
| .pi-log-entry:hover { background: #16162080; } | |
| .pi-log-pos { | |
| color: var(--dim); | |
| text-align: right; | |
| font-size: 0.72rem; | |
| } | |
| .pi-log-number { | |
| color: var(--text); | |
| word-break: break-all; | |
| line-height: 1.4; | |
| } | |
| .pi-log-badge { | |
| font-size: 0.7rem; | |
| font-weight: 600; | |
| padding: 0.25rem 0.6rem; | |
| border-radius: 20px; | |
| white-space: nowrap; | |
| } | |
| .badge-prime { | |
| background: var(--green-glow); | |
| color: var(--green); | |
| border: 1px solid var(--green); | |
| } | |
| .badge-composite { | |
| background: #1a1a2a; | |
| color: var(--dim); | |
| border: 1px solid var(--border); | |
| } | |
| .badge-even { | |
| background: transparent; | |
| color: #333; | |
| border: 1px solid #1a1a24; | |
| font-size: 0.65rem; | |
| } | |
| .badge-testing { | |
| background: var(--amber-glow); | |
| color: var(--amber); | |
| border: 1px solid var(--amber); | |
| } | |
| /* Prime celebration */ | |
| .pi-log-entry.is-prime-entry { | |
| background: linear-gradient(90deg, var(--green-glow), transparent); | |
| border: 1px solid var(--green); | |
| } | |
| .pi-log-entry.is-prime-entry .pi-log-number { | |
| color: var(--green); | |
| font-weight: 600; | |
| } | |
| /* Prime discovery flash */ | |
| @keyframes primeFlash { | |
| 0% { box-shadow: 0 0 0 0 var(--green); } | |
| 50% { box-shadow: 0 0 30px 5px var(--green-glow); } | |
| 100% { box-shadow: 0 0 0 0 transparent; } | |
| } | |
| .prime-flash { | |
| animation: primeFlash 1s ease; | |
| } | |
| /* Responsive */ | |
| @media (max-width: 600px) { | |
| h1 { font-size: 1.8rem; } | |
| .input-row { flex-direction: column; } | |
| .container { padding: 1.5rem 1rem; } | |
| .pi-symbol { font-size: 3.5rem; } | |
| .pi-stats { grid-template-columns: repeat(2, 1fr); } | |
| .pi-log-entry { grid-template-columns: 45px 1fr auto; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <header> | |
| <h1>Miller-Rabin</h1> | |
| <p>Watch the probabilistic primality test work step-by-step on numbers with hundreds of digits.</p> | |
| </header> | |
| <div class="input-section"> | |
| <label>Number to test</label> | |
| <div class="input-row"> | |
| <input type="text" id="numberInput" placeholder="Enter a large number or try a preset..." spellcheck="false" /> | |
| <button class="btn btn-primary" id="runBtn" onclick="runTest()">Test</button> | |
| </div> | |
| <div class="presets"> | |
| <button class="preset-btn" onclick="loadPreset('mersenne31')">2<sup>31</sup> - 1</button> | |
| <button class="preset-btn" onclick="loadPreset('mersenne61')">2<sup>61</sup> - 1</button> | |
| <button class="preset-btn" onclick="loadPreset('mersenne127')">2<sup>127</sup> - 1</button> | |
| <button class="preset-btn" onclick="loadPreset('mersenne521')">2<sup>521</sup> - 1</button> | |
| <button class="preset-btn" onclick="loadPreset('rsa100')">RSA-100</button> | |
| <button class="preset-btn" onclick="loadPreset('carmichael')">Carmichael 561</button> | |
| <button class="preset-btn" onclick="loadPreset('big_composite')">Big Composite</button> | |
| <button class="preset-btn" onclick="loadPreset('random_prime')">Random ~200 digits</button> | |
| </div> | |
| </div> | |
| <div class="decomposition" id="decomposition"> | |
| <div class="section-title"><span class="step-num">1</span> Decompose n - 1 as 2<sup>s</sup> · d</div> | |
| <div class="math-display" id="decompDisplay"></div> | |
| </div> | |
| <div class="running-indicator" id="runningIndicator"> | |
| <div class="spinner"></div> | |
| <span>Testing witnesses...</span> | |
| </div> | |
| <div class="rounds-container" id="roundsContainer"></div> | |
| <div class="final-result" id="finalResult"> | |
| <div class="result-icon" id="resultIcon"></div> | |
| <div class="result-text" id="resultText"></div> | |
| <div class="result-sub" id="resultSub"></div> | |
| <div class="confidence" id="confidence"></div> | |
| </div> | |
| <!-- ── Pi Prime Hunt ───────────────────────── --> | |
| <div class="pi-divider"> | |
| <span class="pi-divider-label">Part II</span> | |
| </div> | |
| <div class="pi-header"> | |
| <div class="pi-symbol">π</div> | |
| <h2>The Pi Prime Hunt</h2> | |
| <p>Take the first N digits of π as an integer. Is it prime? Keep adding digits and test each odd number with Miller-Rabin. How far can we go?</p> | |
| </div> | |
| <div class="pi-controls"> | |
| <button class="btn btn-primary" id="piStartBtn" onclick="startPiHunt()">Start Hunt</button> | |
| <button class="btn btn-stop" id="piStopBtn" onclick="stopPiHunt()" style="display:none">Stop</button> | |
| <div class="speed-control"> | |
| <span>Speed:</span> | |
| <select id="piSpeed"> | |
| <option value="slow">Slow</option> | |
| <option value="normal" selected>Normal</option> | |
| <option value="fast">Fast</option> | |
| <option value="ludicrous">Ludicrous</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="pi-stats"> | |
| <div class="pi-stat stat-position"> | |
| <div class="pi-stat-value" id="piStatPos">0</div> | |
| <div class="pi-stat-label">Digits scanned</div> | |
| </div> | |
| <div class="pi-stat stat-primes"> | |
| <div class="pi-stat-value" id="piStatPrimes">0</div> | |
| <div class="pi-stat-label">Primes found</div> | |
| </div> | |
| <div class="pi-stat stat-composites"> | |
| <div class="pi-stat-value" id="piStatComposites">0</div> | |
| <div class="pi-stat-label">Composite</div> | |
| </div> | |
| <div class="pi-stat stat-skipped"> | |
| <div class="pi-stat-value" id="piStatSkipped">0</div> | |
| <div class="pi-stat-label">Even (skipped)</div> | |
| </div> | |
| </div> | |
| <div class="pi-digit-display" id="piDigitDisplay" style="display:none"> | |
| <div class="section-title">π digits</div> | |
| <div class="pi-digits-scroll" id="piDigitsScroll"></div> | |
| </div> | |
| <div class="pi-results-log" id="piResultsLog" style="display:none"> | |
| <div class="section-title" style="padding: 1.2rem 1.5rem 0.6rem;">Results</div> | |
| <div class="pi-log-list" id="piLogList"></div> | |
| </div> | |
| </div> | |
| <script> | |
| // BigInt helpers | |
| function bigRandom(min, max) { | |
| const range = max - min; | |
| const bits = range.toString(2).length; | |
| const bytes = Math.ceil(bits / 8); | |
| let result; | |
| do { | |
| const arr = new Uint8Array(bytes); | |
| crypto.getRandomValues(arr); | |
| result = BigInt('0x' + Array.from(arr).map(b => b.toString(16).padStart(2, '0')).join('')); | |
| result = result % range; | |
| } while (result < 0n); | |
| return result + min; | |
| } | |
| function modPow(base, exp, mod) { | |
| let result = 1n; | |
| base = base % mod; | |
| while (exp > 0n) { | |
| if (exp % 2n === 1n) result = (result * base) % mod; | |
| exp = exp / 2n; | |
| base = (base * base) % mod; | |
| } | |
| return result; | |
| } | |
| function formatBigNum(n) { | |
| const s = n.toString(); | |
| if (s.length <= 20) return `<span class="digit-group">${s}</span>`; | |
| const groups = []; | |
| for (let i = 0; i < s.length; i += 4) { | |
| groups.push(`<span class="digit-group">${s.slice(i, i + 4)}</span>`); | |
| } | |
| return groups.join('<wbr>'); | |
| } | |
| function truncNum(n, maxLen = 40) { | |
| const s = n.toString(); | |
| if (s.length <= maxLen) return s; | |
| const half = Math.floor((maxLen - 3) / 2); | |
| return s.slice(0, half) + '...' + s.slice(-half); | |
| } | |
| // Presets | |
| const presets = { | |
| mersenne31: (2n ** 31n - 1n).toString(), | |
| mersenne61: (2n ** 61n - 1n).toString(), | |
| mersenne127: (2n ** 127n - 1n).toString(), | |
| mersenne521: (2n ** 521n - 1n).toString(), | |
| rsa100: '1522605027922533360535618378132637429718068114961380688657908494580122963258952897654000350692006139', | |
| carmichael: '561', | |
| big_composite: (2n ** 521n - 1n - 6n).toString(), | |
| random_prime: null, | |
| }; | |
| function generateProbablePrime(digits) { | |
| // generate a random odd number of ~digits length and return it | |
| // (it might not be prime, but that's fine — the test will reveal that) | |
| let s = ''; | |
| s += (Math.floor(Math.random() * 9) + 1).toString(); | |
| for (let i = 1; i < digits; i++) s += Math.floor(Math.random() * 10).toString(); | |
| let n = BigInt(s); | |
| if (n % 2n === 0n) n += 1n; | |
| return n.toString(); | |
| } | |
| function loadPreset(key) { | |
| const input = document.getElementById('numberInput'); | |
| if (key === 'random_prime') { | |
| input.value = generateProbablePrime(200); | |
| } else { | |
| input.value = presets[key]; | |
| } | |
| } | |
| // State | |
| let isRunning = false; | |
| async function runTest() { | |
| if (isRunning) return; | |
| const input = document.getElementById('numberInput').value.trim().replace(/[^0-9]/g, ''); | |
| if (!input || input === '0' || input === '1') return; | |
| const n = BigInt(input); | |
| if (n < 2n) return; | |
| if (n === 2n || n === 3n) { | |
| showResult(true, n, 0, 0); | |
| return; | |
| } | |
| if (n % 2n === 0n) { | |
| showResult(false, n, 0, 0, 'It is even.'); | |
| return; | |
| } | |
| isRunning = true; | |
| document.getElementById('runBtn').disabled = true; | |
| document.getElementById('roundsContainer').innerHTML = ''; | |
| document.getElementById('roundsContainer').classList.remove('visible'); | |
| document.getElementById('finalResult').classList.remove('visible'); | |
| // Decompose n-1 = 2^s * d | |
| let d = n - 1n; | |
| let s = 0n; | |
| while (d % 2n === 0n) { | |
| d /= 2n; | |
| s++; | |
| } | |
| showDecomposition(n, s, d); | |
| await sleep(600); | |
| document.getElementById('runningIndicator').classList.add('visible'); | |
| document.getElementById('roundsContainer').classList.add('visible'); | |
| const k = 12; // number of witnesses | |
| let allPassed = true; | |
| const witnesses = []; | |
| // Pick witnesses | |
| if (n < 100n) { | |
| // small numbers: use 2, 3, etc | |
| for (let a = 2n; a < n - 1n && witnesses.length < k; a++) witnesses.push(a); | |
| } else { | |
| // First few deterministic witnesses, then random | |
| const fixed = [2n, 3n, 5n, 7n, 11n, 13n]; | |
| for (const a of fixed) { if (a < n - 1n) witnesses.push(a); } | |
| while (witnesses.length < k) { | |
| witnesses.push(bigRandom(14n, n - 2n)); | |
| } | |
| } | |
| for (let i = 0; i < witnesses.length; i++) { | |
| const a = witnesses[i]; | |
| const result = await testWitness(a, d, s, n, i); | |
| if (!result.passed) { | |
| allPassed = false; | |
| // Composite — stop early | |
| document.getElementById('runningIndicator').classList.remove('visible'); | |
| showResult(false, n, i + 1, k, `Witness a = ${truncNum(a)} proved composite.`); | |
| isRunning = false; | |
| document.getElementById('runBtn').disabled = false; | |
| return; | |
| } | |
| await sleep(200); | |
| } | |
| document.getElementById('runningIndicator').classList.remove('visible'); | |
| showResult(true, n, k, k); | |
| isRunning = false; | |
| document.getElementById('runBtn').disabled = false; | |
| } | |
| function showDecomposition(n, s, d) { | |
| const el = document.getElementById('decomposition'); | |
| el.classList.add('visible'); | |
| const nStr = n.toString(); | |
| const nm1Str = (n - 1n).toString(); | |
| const dStr = d.toString(); | |
| el.querySelector('#decompDisplay').innerHTML = ` | |
| <div><span class="label">n =</span> <span class="big-number">${formatBigNum(n)}</span></div> | |
| <div class="digit-count">${nStr.length} digits</div> | |
| <br> | |
| <div><span class="label">n - 1 =</span> <span class="big-number">${formatBigNum(n - 1n)}</span></div> | |
| <br> | |
| <div> | |
| <span class="label">n - 1 =</span> | |
| <span class="operator">2</span><sup class="highlight-s">${s.toString()}</sup> | |
| <span class="operator">×</span> | |
| <span class="highlight-d">${truncNum(d, 80)}</span> | |
| </div> | |
| <div style="margin-top: 0.4rem;"> | |
| <span class="label">s =</span> <span class="highlight-s">${s.toString()}</span> | |
| | |
| <span class="label">d =</span> <span class="highlight-d">${dStr.length > 60 ? dStr.length + '-digit odd number' : dStr}</span> | |
| </div> | |
| `; | |
| } | |
| async function testWitness(a, d, s, n, roundIdx) { | |
| const container = document.getElementById('roundsContainer'); | |
| const round = document.createElement('div'); | |
| round.className = 'witness-round'; | |
| container.appendChild(round); | |
| const header = document.createElement('div'); | |
| header.className = 'round-header'; | |
| header.innerHTML = ` | |
| <div class="round-header-left"> | |
| <span class="round-number">Round ${roundIdx + 1}</span> | |
| <span class="witness-value">a = <span>${truncNum(a, 50)}</span></span> | |
| </div> | |
| `; | |
| round.appendChild(header); | |
| const body = document.createElement('div'); | |
| body.className = 'round-body open'; | |
| round.appendChild(body); | |
| const pipeline = document.createElement('div'); | |
| pipeline.className = 'step-pipeline'; | |
| body.appendChild(pipeline); | |
| header.onclick = () => body.classList.toggle('open'); | |
| // Step 1: Compute a^d mod n | |
| const x0 = modPow(a, d, n); | |
| const step1 = addPipelineStep(pipeline, 'Initial computation', ` | |
| a<sup>d</sup> mod n <span class="eq">=</span> ${truncNum(a, 20)}<sup>${truncNum(d, 20)}</sup> mod ${truncNum(n, 20)} | |
| <br><span class="eq">=</span> <span class="val">${truncNum(x0, 60)}</span> | |
| `); | |
| await sleep(300); | |
| // Check if x0 = 1 or x0 = n-1 | |
| if (x0 === 1n) { | |
| step1.classList.add('step-pass'); | |
| addPipelineStep(pipeline, 'Result', ` | |
| <span class="val">${truncNum(x0)}</span> <span class="eq">===</span> 1 <span class="yes">→ probably prime</span> | |
| `, 'step-pass'); | |
| addVerdict(round, header, true); | |
| return { passed: true, reason: 'x0 === 1' }; | |
| } | |
| if (x0 === n - 1n) { | |
| step1.classList.add('step-pass'); | |
| addPipelineStep(pipeline, 'Result', ` | |
| <span class="val">${truncNum(x0)}</span> <span class="eq">===</span> n - 1 <span class="yes">→ probably prime</span> | |
| `, 'step-pass'); | |
| addVerdict(round, header, true); | |
| return { passed: true, reason: 'x0 === n-1' }; | |
| } | |
| step1.classList.add('step-neutral'); | |
| addPipelineStep(pipeline, 'Check', ` | |
| <span class="val">${truncNum(x0)}</span> | |
| <span class="cond">≠ 1 and ≠ n-1 → proceed to squaring</span> | |
| `, 'step-neutral'); | |
| await sleep(200); | |
| // Squaring phase | |
| const sqStep = addPipelineStep(pipeline, `Repeated squaring (up to s-1 = ${(s - 1n).toString()} times)`, '', 'step-active'); | |
| let x = x0; | |
| let found = false; | |
| const grid = document.createElement('div'); | |
| grid.className = 'squaring-grid'; | |
| sqStep.querySelector('.step-math').appendChild(grid); | |
| for (let r = 1n; r < s; r++) { | |
| x = (x * x) % n; | |
| const iterEl = document.createElement('span'); | |
| iterEl.className = 'sq-iter'; | |
| iterEl.textContent = `r=${r}`; | |
| const valEl = document.createElement('span'); | |
| valEl.className = 'sq-val'; | |
| valEl.textContent = truncNum(x, 60); | |
| const checkEl = document.createElement('span'); | |
| checkEl.className = 'sq-check'; | |
| if (x === n - 1n) { | |
| checkEl.classList.add('hit'); | |
| checkEl.textContent = '= n-1 ✓'; | |
| found = true; | |
| } else if (x === 1n) { | |
| checkEl.classList.add('miss'); | |
| checkEl.textContent = '= 1 (non-trivial sqrt!)'; | |
| } else { | |
| checkEl.classList.add('miss'); | |
| checkEl.textContent = '—'; | |
| } | |
| grid.appendChild(iterEl); | |
| grid.appendChild(valEl); | |
| grid.appendChild(checkEl); | |
| await sleep(150); | |
| if (found) break; | |
| } | |
| if (found) { | |
| sqStep.classList.remove('step-active'); | |
| sqStep.classList.add('step-pass'); | |
| addPipelineStep(pipeline, 'Conclusion', ` | |
| Found x <span class="eq">===</span> n - 1 during squaring <span class="yes">→ probably prime</span> | |
| `, 'step-pass'); | |
| addVerdict(round, header, true); | |
| return { passed: true, reason: 'found n-1 in squaring' }; | |
| } else { | |
| sqStep.classList.remove('step-active'); | |
| sqStep.classList.add('step-fail'); | |
| addPipelineStep(pipeline, 'Conclusion', ` | |
| Never reached 1 or n - 1 <span class="no">→ COMPOSITE (definitive)</span> | |
| `, 'step-fail'); | |
| addVerdict(round, header, false); | |
| return { passed: false, reason: 'never hit n-1' }; | |
| } | |
| } | |
| function addPipelineStep(pipeline, label, mathHTML, cls = '') { | |
| const step = document.createElement('div'); | |
| step.className = `pipeline-step ${cls}`; | |
| step.innerHTML = ` | |
| <div class="step-label">${label}</div> | |
| <div class="step-math">${mathHTML}</div> | |
| `; | |
| pipeline.appendChild(step); | |
| return step; | |
| } | |
| function addVerdict(round, header, passed) { | |
| const badge = document.createElement('span'); | |
| badge.className = `round-verdict ${passed ? 'verdict-pass' : 'verdict-fail'}`; | |
| badge.textContent = passed ? 'PASS' : 'COMPOSITE'; | |
| header.appendChild(badge); | |
| } | |
| function showResult(isPrime, n, rounds, totalRounds, reason) { | |
| const el = document.getElementById('finalResult'); | |
| el.className = `final-result visible ${isPrime ? 'is-prime' : 'is-composite'}`; | |
| document.getElementById('resultIcon').textContent = isPrime ? '◆' : '✕'; | |
| document.getElementById('resultText').textContent = isPrime ? 'Probably Prime' : 'Composite'; | |
| if (isPrime && rounds > 0) { | |
| const errorProb = Math.pow(4, -rounds); | |
| const nines = Math.min(Math.floor(-Math.log10(errorProb)), 99); | |
| document.getElementById('resultSub').textContent = | |
| `Passed all ${rounds} witness rounds.`; | |
| document.getElementById('confidence').innerHTML = ` | |
| Confidence: ${nines > 50 ? '>99.99...%' : (100 * (1 - errorProb)).toFixed(Math.min(nines + 1, 15)) + '%'} | |
| <br><span style="color: var(--dim)">Error probability ≤ 4<sup>-${rounds}</sup> ≈ ${errorProb.toExponential(2)}</span> | |
| <div class="confidence-bar"><div class="confidence-fill" style="width: 100%"></div></div> | |
| `; | |
| } else if (isPrime) { | |
| document.getElementById('resultSub').textContent = 'Trivially prime.'; | |
| document.getElementById('confidence').innerHTML = `Deterministic — 100% certain`; | |
| } else { | |
| document.getElementById('resultSub').textContent = reason || 'Definitively not prime.'; | |
| document.getElementById('confidence').innerHTML = | |
| `<span style="color: var(--red)">Composite — no probability involved. This is certain.</span>`; | |
| } | |
| } | |
| function sleep(ms) { | |
| return new Promise(resolve => setTimeout(resolve, ms)); | |
| } | |
| // Enter key | |
| document.getElementById('numberInput').addEventListener('keydown', e => { | |
| if (e.key === 'Enter') runTest(); | |
| }); | |
| // ── Pi Prime Hunt ───────────────────────────────────── | |
| // 1000 digits of pi (no decimal point) | |
| const PI_DIGITS = '3141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273724587006606315588174881520920962829254091715364367892590360011330530548820466521384146951941511609433057270365759591953092186117381932611793105118548074462379962749567351885752724891227938183011949129833673362440656643086021394946395224737190702179860943702770539217176293176752384674818467669405132000568127145263560827785771342757789609173637178721468440901224953430146549585371050792279689258923542019956112129021960864034418159813629774771309960518707211349999998372978049951059731732816096318595024459455346908302642522308253344685035261931188171010003137838752886587533208381420617177669147303598253490428755468731159562863882353787593751957781857780532171226806613001927876611195909216420198'; | |
| let piHuntRunning = false; | |
| let piHuntStopped = false; | |
| let piPrimesFound = []; | |
| // Fast (non-animated) Miller-Rabin for the pi hunt | |
| function millerRabinFast(n, k = 12) { | |
| if (n < 2n) return false; | |
| if (n === 2n || n === 3n) return true; | |
| if (n % 2n === 0n) return false; | |
| let d = n - 1n; | |
| let s = 0n; | |
| while (d % 2n === 0n) { d /= 2n; s++; } | |
| const witnesses = []; | |
| const fixed = [2n, 3n, 5n, 7n, 11n, 13n, 17n, 19n, 23n, 29n, 31n, 37n]; | |
| for (const a of fixed) { | |
| if (a < n - 1n) witnesses.push(a); | |
| if (witnesses.length >= k) break; | |
| } | |
| while (witnesses.length < k && n > 40n) { | |
| witnesses.push(bigRandom(38n, n - 2n)); | |
| } | |
| for (const a of witnesses) { | |
| let x = modPow(a, d, n); | |
| if (x === 1n || x === n - 1n) continue; | |
| let found = false; | |
| for (let r = 1n; r < s; r++) { | |
| x = (x * x) % n; | |
| if (x === n - 1n) { found = true; break; } | |
| } | |
| if (!found) return false; | |
| } | |
| return true; | |
| } | |
| function getSpeedDelay() { | |
| const v = document.getElementById('piSpeed').value; | |
| return { slow: 500, normal: 150, fast: 30, ludicrous: 1 }[v]; | |
| } | |
| function renderPiDigits(position, primePositions) { | |
| const scroll = document.getElementById('piDigitsScroll'); | |
| // Show a window of digits around current position | |
| const displayLen = Math.min(PI_DIGITS.length, Math.max(position + 100, 200)); | |
| let html = '<span class="d-decimal">π = </span>'; | |
| for (let i = 0; i < displayLen; i++) { | |
| if (i === 1) html += '<span class="d-decimal">.</span>'; // decimal point after first digit | |
| if (i < position) { | |
| // check if this position was a prime endpoint | |
| if (primePositions.has(i + 1)) { | |
| html += `<span class="d-prime">${PI_DIGITS[i]}</span>`; | |
| } else { | |
| html += `<span class="d-consumed">${PI_DIGITS[i]}</span>`; | |
| } | |
| } else if (i === position) { | |
| html += `<span class="d-current">${PI_DIGITS[i]}</span>`; | |
| } else { | |
| html += `<span class="d-upcoming">${PI_DIGITS[i]}</span>`; | |
| } | |
| // spacing every 10 digits for readability | |
| if ((i + 1) % 10 === 0 && i > 0 && i < displayLen - 1) html += ' '; | |
| if ((i + 1) % 50 === 0 && i > 0 && i < displayLen - 1) html += '<br>'; | |
| } | |
| if (displayLen < PI_DIGITS.length) html += '<span class="d-upcoming">...</span>'; | |
| scroll.innerHTML = html; | |
| // Auto-scroll to keep the current position visible | |
| const currentEl = scroll.querySelector('.d-current'); | |
| if (currentEl) currentEl.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); | |
| } | |
| function addPiLogEntry(pos, numberStr, status, isPrime) { | |
| const list = document.getElementById('piLogList'); | |
| const entry = document.createElement('div'); | |
| entry.className = `pi-log-entry ${isPrime ? 'is-prime-entry prime-flash' : ''}`; | |
| let badgeClass, badgeText; | |
| if (status === 'even') { badgeClass = 'badge-even'; badgeText = 'EVEN'; } | |
| else if (status === 'prime') { badgeClass = 'badge-prime'; badgeText = 'PRIME'; } | |
| else if (status === 'composite') { badgeClass = 'badge-composite'; badgeText = 'COMP'; } | |
| else { badgeClass = 'badge-testing'; badgeText = 'TEST...'; } | |
| // Truncate display for very long numbers | |
| let displayNum; | |
| if (numberStr.length <= 50) { | |
| displayNum = numberStr; | |
| } else { | |
| displayNum = numberStr.slice(0, 24) + '<span style="color:var(--dim)">...' + numberStr.length + ' digits...</span>' + numberStr.slice(-12); | |
| } | |
| entry.innerHTML = ` | |
| <div class="pi-log-pos">${pos} digit${pos > 1 ? 's' : ''}</div> | |
| <div class="pi-log-number">${displayNum}</div> | |
| <div><span class="pi-log-badge ${badgeClass}">${badgeText}</span></div> | |
| `; | |
| // Insert at top for newest-first | |
| list.insertBefore(entry, list.firstChild); | |
| return entry; | |
| } | |
| function updatePiStats(pos, primes, composites, skipped) { | |
| document.getElementById('piStatPos').textContent = pos; | |
| document.getElementById('piStatPrimes').textContent = primes; | |
| document.getElementById('piStatComposites').textContent = composites; | |
| document.getElementById('piStatSkipped').textContent = skipped; | |
| } | |
| async function startPiHunt() { | |
| if (piHuntRunning) return; | |
| piHuntRunning = true; | |
| piHuntStopped = false; | |
| document.getElementById('piStartBtn').style.display = 'none'; | |
| document.getElementById('piStopBtn').style.display = ''; | |
| document.getElementById('piDigitDisplay').style.display = ''; | |
| document.getElementById('piResultsLog').style.display = ''; | |
| document.getElementById('piLogList').innerHTML = ''; | |
| let primesCount = 0, compositesCount = 0, skippedCount = 0; | |
| const primePositions = new Set(); | |
| piPrimesFound = []; | |
| for (let pos = 1; pos <= PI_DIGITS.length; pos++) { | |
| if (piHuntStopped) break; | |
| const numStr = PI_DIGITS.slice(0, pos); | |
| const n = BigInt(numStr); | |
| const lastDigit = parseInt(PI_DIGITS[pos - 1]); | |
| renderPiDigits(pos, primePositions); | |
| if (pos > 1 && lastDigit % 2 === 0) { | |
| // Even — skip | |
| skippedCount++; | |
| addPiLogEntry(pos, numStr, 'even', false); | |
| updatePiStats(pos, primesCount, compositesCount, skippedCount); | |
| const delay = getSpeedDelay(); | |
| if (delay > 1) await sleep(Math.max(delay / 3, 1)); | |
| continue; | |
| } | |
| // Also skip if ends in 5 and length > 1 (divisible by 5) | |
| if (pos > 1 && lastDigit === 5) { | |
| compositesCount++; | |
| addPiLogEntry(pos, numStr, 'composite', false); | |
| updatePiStats(pos, primesCount, compositesCount, skippedCount); | |
| const delay = getSpeedDelay(); | |
| if (delay > 1) await sleep(Math.max(delay / 3, 1)); | |
| continue; | |
| } | |
| // Odd number — run Miller-Rabin | |
| const testEntry = addPiLogEntry(pos, numStr, 'testing', false); | |
| updatePiStats(pos, primesCount, compositesCount, skippedCount); | |
| // Yield to browser before heavy computation | |
| await sleep(1); | |
| const isPrime = millerRabinFast(n); | |
| // Replace the "testing" entry with the result | |
| testEntry.remove(); | |
| if (isPrime) { | |
| primesCount++; | |
| primePositions.add(pos); | |
| piPrimesFound.push({ pos, numStr }); | |
| addPiLogEntry(pos, numStr, 'prime', true); | |
| renderPiDigits(pos, primePositions); | |
| // Longer pause to celebrate finding a prime | |
| const delay = getSpeedDelay(); | |
| await sleep(Math.max(delay * 3, 100)); | |
| } else { | |
| compositesCount++; | |
| addPiLogEntry(pos, numStr, 'composite', false); | |
| const delay = getSpeedDelay(); | |
| if (delay > 1) await sleep(delay); | |
| } | |
| updatePiStats(pos, primesCount, compositesCount, skippedCount); | |
| } | |
| piHuntRunning = false; | |
| document.getElementById('piStartBtn').style.display = ''; | |
| document.getElementById('piStartBtn').textContent = 'Restart Hunt'; | |
| document.getElementById('piStopBtn').style.display = 'none'; | |
| } | |
| function stopPiHunt() { | |
| piHuntStopped = true; | |
| piHuntRunning = false; | |
| document.getElementById('piStartBtn').style.display = ''; | |
| document.getElementById('piStartBtn').textContent = 'Restart Hunt'; | |
| document.getElementById('piStopBtn').style.display = 'none'; | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment