<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>21-bit Converter + Disambiguated Reverse</title>
<style>
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial; margin: 24px; color:#111; }
h1 { margin-bottom: 8px; font-size: 20px; }
.card { border:1px solid #e1e4e8; border-radius:8px; padding:16px; max-width:880px; background:#fff; }
label { display:block; margin-bottom:8px; font-weight:600; }
input[type="text"], input[type="number"] { padding:10px 12px; font-size:16px; border:1px solid #cbd5e1; border-radius:6px; box-sizing:border-box; }
input#bits { width:100%; }
button { margin-top:12px; padding:8px 12px; font-size:15px; border-radius:6px; border:0; background:#0366d6; color:#fff; cursor:pointer; }
button.secondary { background:#6b7280; }
.error { color:#b91c1c; margin-top:8px; }
pre { background:#f6f8fa; padding:12px; border-radius:6px; overflow:auto; font-size:13px; }
.steps { margin-top:12px; }
.result { margin-top:10px; font-weight:700; }
.small { font-size:13px; color:#374151; }
.row { display:flex; gap:8px; align-items:center; margin-top:8px; flex-wrap:wrap; }
.ok { color:#15803d; } .bad { color:#b91c1c; }
.note { background:#fff7ed; border:1px solid #fdba74; border-radius:6px; padding:10px 12px; font-size:13px; margin-top:12px; }
</style>
</head>
<body>
<div class="card">
<h1>21-bit Converter + disambiguated reverse</h1>
<div class="small">Forward: start at 2 or 3 (bit 0), then ×2 (bit 0) or ×2−1 (bit 1) for the other 20 bits, subtract 1,048,576.
Reverse: parity-invert. When the value is 2 the start is certain (stop). When it is 3 it is ambiguous — could be the
real start or an intermediate 3 that came from 2 — so one disambiguation bit says stop or continue. With that bit the
reverse is always correct.</div>
<label for="bits" style="margin-top:12px">Bits (exactly 21)</label>
<input id="bits" type="text" placeholder="e.g. 010101001110000111000" maxlength="21" />
<div class="row">
<button id="compute">Compute + reverse</button>
<button id="random" class="secondary">Random</button>
<button id="example" class="secondary">Fill Example</button>
</div>
<div style="margin-top:14px;">
<label for="count">Run random trials (count)</label>
<div class="row">
<input id="count" type="number" min="1" max="2000000" value="100000" style="width:140px;" />
<button id="runTests">Run trials</button>
</div>
</div>
<div id="error" class="error" aria-live="polite"></div>
<div id="output" class="steps" aria-live="polite"></div>
<div class="note">The disambiguation bit is charged only when the value passes through 3: about a quarter of inputs
need zero, about three quarters need one. The reverse is now correct on every input; the trials show what that
correctness costs on average, on top of the 21 input bits.</div>
</div>
<script>
const $ = id => document.getElementById(id);
const bitsInput=$('bits'), errorEl=$('error'), outputEl=$('output'), countInput=$('count');
const SUBTRACT = 1024n * 1024n;
function validateBits(s){ if(!s) return 'Input is empty'; if(s.length!==21) return 'Input must be exactly 21 bits'; if(!/^[01]{21}$/.test(s)) return 'Input must contain only 0 and 1'; return ''; }
// Forward — unchanged from your method.
function forward(bitString){
const bits = bitString.split('').map(b => b==='1'?1:0);
let value = bits[0]===0 ? 2n : 3n;
for(let i=1;i<bits.length;i++){ value = bits[i]===0 ? value*2n : value*2n - 1n; }
return { before: value, after: value - SUBTRACT };
}
// Encoder side: the disambiguation bits for an input (simulate the true 20-step reverse).
function disambigBits(bitString){
let v = Number(forward(bitString).before); const dis = [];
for(let step=0; step<20; step++){ if(v===3) dis.push('1'); v = (v%2===0) ? v/2 : (v+1)/2; }
if(v===3) dis.push('0'); // terminal start is 3 -> stop
return dis.join('');
}
// Decoder: parity-invert V, reading a disambiguation bit at each 3 (0 stop, 1 continue). Value 2 is an unambiguous start.
function decode(Vnum, dis){
let v=Vnum, out=[], di=0, steps=0, start=null;
while(steps<200){
if(v===2){ start='0'; break; }
if(v===3){ const d=dis[di++]; if(d==='0'){ start='1'; break; } }
if(v%2===0){ out.push('0'); v=v/2; } else { out.push('1'); v=(v+1)/2; }
steps++;
}
if(start===null) return null;
return start + out.reverse().join('');
}
$('compute').addEventListener('click', () => {
errorEl.textContent=''; outputEl.innerHTML='';
const s = bitsInput.value.trim(); const v = validateBits(s);
if(v){ errorEl.textContent=v; return; }
const f = forward(s);
const dis = disambigBits(s);
const rec = decode(Number(f.before), dis);
const match = rec===s;
let html='';
html += `<div class="result">Value before subtracting: ${f.before.toString()}</div>`;
html += `<div class="result">Final output: ${f.after.toString()}</div>`;
html += `<div class="result">Disambiguation bits: <strong>${dis===''?'(none)':dis}</strong> (${dis.length})</div>`;
html += `<div class="result">Reverse: <span class="${match?'ok':'bad'}">${rec===null?'no valid start':rec}</span></div>`;
html += `<div class="small">${match ? '<span class="ok">matches the input exactly</span>' : '<span class="bad">does NOT match</span>'}` +
` · total carried = 21 input bits + ${dis.length} disambiguation = ${21+dis.length} bits</div>`;
outputEl.innerHTML = html;
});
$('example').addEventListener('click', () => { bitsInput.value='010101001110000111000'; errorEl.textContent=''; outputEl.innerHTML=''; });
function randomBits21(){ let s=''; for(let i=0;i<21;i++) s += (Math.random()<0.5?'0':'1'); return s; }
$('random').addEventListener('click', () => { bitsInput.value=randomBits21(); $('compute').click(); });
$('runTests').addEventListener('click', () => {
errorEl.textContent=''; outputEl.innerHTML='';
const count = Math.max(1, Math.min(2000000, Number(countInput.value)||100000));
let correct=0, wrong=0, sumDis=0; const dist={};
for(let i=0;i<count;i++){
const s = randomBits21();
const dis = disambigBits(s);
const rec = decode(Number(forward(s).before), dis);
if(rec===s) correct++; else wrong++;
sumDis += dis.length; dist[dis.length]=(dist[dis.length]||0)+1;
}
const avgDis = sumDis/count;
let html = '<div class="small"><strong>Trial statistics (disambiguated reverse)</strong></div><pre>';
html += `Trials: ${count}\n`;
html += `Reversed CORRECTLY: ${correct} (${(100*correct/count).toFixed(2)}%)\n`;
html += `Reversed wrong: ${wrong}\n`;
html += `Avg disambiguation bits: ${avgDis.toFixed(4)} per input\n`;
html += `Avg TOTAL carried: ${(21+avgDis).toFixed(4)} bits (21 input + disambiguation)\n`;
html += `Disambig distribution: ${Object.keys(dist).sort((a,b)=>a-b).map(k=>k+' bit'+(k==1?'':'s')+': '+dist[k]).join(' ')}\n`;
html += '</pre>';
html += `<div class="small">The reverse is now correct on every input. The price is the disambiguation bits: the total sits above 21, not below.</div>`;
outputEl.innerHTML = html;
});
bitsInput.addEventListener('keydown', e => { if(e.key==='Enter') $('compute').click(); });
</script>
</body>
</html>