COmpREss.html
- <!doctype html>
- <html lang="en">
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width,initial-scale=1" />
- <title>21-bit to Integer Converter — output is post-subtract binary (LSB disamb, leading 1-bit stripped)</title>
- <style>
- body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial; margin: 24px; color:#111; }
- h1 { margin-bottom: 8px; }
- h2 { margin-top: 24px; margin-bottom: 8px; font-size: 20px; }
- .card { border:1px solid #e1e4e8; border-radius:8px; padding:16px; max-width:920px; background:#fff; box-shadow:0 1px 2px rgba(0,0,0,0.03); margin-bottom: 16px; }
- label { display:block; margin-bottom:8px; font-weight:600; }
- input[type="text"], input[type="number"] { width:100%; padding:10px 12px; font-size:16px; border:1px solid #cbd5e1; border-radius:6px; box-sizing:border-box; }
- 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; }
- .success { color:#059669; margin-top:8px; font-weight:600; }
- pre { background:#f6f8fa; padding:12px; border-radius:6px; overflow:auto; }
- .steps { margin-top:12px; }
- .result { margin-top:12px; font-weight:700; font-size:18px; }
- .small { font-size:13px; color:#374151; }
- .row { display:flex; gap:8px; align-items:center; margin-top:8px; }
- .copy { background:#10b981; }
- .inline { display:inline-block; width:auto; }
- .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, "Roboto Mono", "Courier New", monospace; }
- </style>
- </head>
- <body>
- <div class="card">
- <h1>21-bit to Integer Converter</h1>
- <div class="small">
- Enter exactly 21 bits. The calculator follows your rule set and <strong>subtracts 1,048,576</strong>.
- <strong>Output</strong> is the binary representation of the value <em>after</em> that subtraction, with the leading 1-bit stripped (decompressor adds it back), and a single
- deterministic disambiguation bit appended as the LSB. All UI elements and trial statistics use that same total length (core bits without leading 1 + 1 LSB).
- </div>
- <label for="bits">Bits</label>
- <input id="bits" type="text" placeholder="e.g. 010101001110000111000" maxlength="21" />
- <div class="row">
- <button id="compute">Compute</button>
- <button id="random" class="secondary">Random</button>
- <button id="example" class="secondary">Fill Example</button>
- <button id="copy" class="copy">Copy Result</button>
- </div>
- <div style="margin-top:12px;">
- <label for="count">Run Random Tests (count)</label>
- <div class="row">
- <input id="count" type="number" min="1" max="100000" value="100" style="width:120px;" />
- <button id="runTests">Run</button>
- </div>
- </div>
- <div id="error" class="error" role="status" aria-live="polite"></div>
- <div id="output" class="steps" aria-live="polite"></div>
- </div>
- <div class="card">
- <h2>Decompressor (Test Reversibility)</h2>
- <div class="small">
- Enter a compressed payload (binary with leading 1-bit stripped + LSB disambiguation bit) to decompress it back to the original 21-bit input.
- </div>
- <label for="compressed">Compressed Payload</label>
- <input id="compressed" type="text" placeholder="e.g. 01010100111000011100" />
- <div class="row">
- <button id="decompress">Decompress</button>
- <button id="testRoundtrip" class="secondary">Test Roundtrip</button>
- </div>
- <div id="decompressError" class="error" role="status" aria-live="polite"></div>
- <div id="decompressOutput" class="steps" aria-live="polite"></div>
- </div>
- <script>
- const bitsInput = document.getElementById('bits');
- const computeBtn = document.getElementById('compute');
- const randomBtn = document.getElementById('random');
- const exampleBtn = document.getElementById('example');
- const copyBtn = document.getElementById('copy');
- const runTestsBtn = document.getElementById('runTests');
- const countInput = document.getElementById('count');
- const errorEl = document.getElementById('error');
- const outputEl = document.getElementById('output');
- const compressedInput = document.getElementById('compressed');
- const decompressBtn = document.getElementById('decompress');
- const testRoundtripBtn = document.getElementById('testRoundtrip');
- const decompressErrorEl = document.getElementById('decompressError');
- const decompressOutputEl = document.getElementById('decompressOutput');
- 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 '';
- }
- function validateCompressed(s) {
- if (!s) return 'Input is empty';
- if (!/^[01]+$/.test(s)) return 'Input must contain only 0 and 1';
- if (s.length < 2) return 'Input must be at least 2 bits (core + LSB)';
- return '';
- }
- const SUBTRACT = 1024n * 1024n; // 1,048,576n
- /**
- * computeFromBits:
- * - runs the stepwise rule to produce valueBeforeSubtract
- * - computes valueAfterSubtract = valueBeforeSubtract - SUBTRACT
- * - produces binary representation of valueAfterSubtract (fullBinaryAfter)
- * - strips the leading 1-bit from fullBinaryAfter to get coreBinaryAfter
- * - appends disambiguation bit (LSB) which is the original first input bit
- *
- * Important: the UI and trial mode treat the "compressed-with-disamb" as:
- * compressedWithDisambLSB = coreBinaryAfter + disambiguationBit
- *
- * The total length in bits reported to the user counts only the binary digits (without leading 1) plus the 1 disambiguation bit.
- */
- function computeFromBits(bitString) {
- const bits = bitString.split('').map(b => b === '1' ? 1 : 0);
- // initial value
- let value = bits[0] === 0 ? 2n : 3n;
- const trace = [];
- trace.push({ step: 0, bit: bits[0], op: `start`, value: value.toString() });
- for (let i = 1; i < bits.length; i++) {
- const b = bits[i];
- if (b === 0) {
- value = value * 2n;
- trace.push({ step: i, bit: 0, op: `×2`, value: value.toString() });
- } else {
- value = value * 2n - 1n;
- trace.push({ step: i, bit: 1, op: `×2 − 1`, value: value.toString() });
- }
- }
- const valueBeforeSubtract = value;
- const valueAfterSubtract = valueBeforeSubtract - SUBTRACT;
- // full binary is the binary representation of the value AFTER subtraction
- const fullBinaryAfter = valueAfterSubtract.toString(2);
- // strip the leading 1-bit (decompressor will add it back)
- const coreBinaryAfter = fullBinaryAfter.slice(1);
- const coreBitsLength = coreBinaryAfter.length;
- // deterministic disambiguation bit: original first input bit
- const disambiguationBit = String(bits[0]);
- // compressed-with-disambiguation (LSB placement) is coreBinaryAfter (without leading 1) + disambiguation bit
- const compressedWithDisambLSB = coreBinaryAfter + disambiguationBit;
- // total length in bits used for statistics: coreBitsLength + 1 (LSB disambiguation)
- const totalLengthBits = coreBitsLength + 1;
- return {
- trace,
- valueBeforeSubtract: valueBeforeSubtract.toString(),
- valueAfterSubtract: valueAfterSubtract.toString(),
- fullBinaryAfter,
- coreBinaryAfter,
- coreBitsLength,
- disambiguationBit,
- compressedWithDisambLSB,
- totalLengthBits,
- originalBits: bitString
- };
- }
- /**
- * decompressPayload:
- * - takes compressed payload (core binary without leading 1 + LSB disambiguation)
- * - extracts LSB disambiguation bit
- * - prepends "1" to restore full binary
- * - parses to get valueAfterSubtract
- * - adds SUBTRACT to get valueBeforeSubtract
- * - reverses the encoding algorithm to recover original 21 bits
- */
- function decompressPayload(compressedPayload) {
- // Extract LSB (last bit)
- const disambiguationBit = compressedPayload[compressedPayload.length - 1];
- // Extract core binary (all but last bit)
- const coreBinary = compressedPayload.slice(0, -1);
- // Restore full binary by prepending "1"
- const fullBinary = '1' + coreBinary;
- // Parse to get valueAfterSubtract
- const valueAfterSubtract = BigInt('0b' + fullBinary);
- // Add SUBTRACT to get valueBeforeSubtract
- const valueBeforeSubtract = valueAfterSubtract + SUBTRACT;
- // Now reverse the encoding algorithm to recover the original 21 bits
- // We need to work backwards from valueBeforeSubtract
- const recovered21Bits = reverseEncode(valueBeforeSubtract);
- return {
- disambiguationBit,
- coreBinary,
- fullBinary,
- valueAfterSubtract: valueAfterSubtract.toString(),
- valueBeforeSubtract: valueBeforeSubtract.toString(),
- recovered21Bits
- };
- }
- /**
- * reverseEncode:
- * Given a valueBeforeSubtract, reverse the encoding algorithm to recover the original 21 bits.
- *
- * The encoding algorithm is:
- * - Start with value = 2 (if first bit is 0) or 3 (if first bit is 1)
- * - For each subsequent bit:
- * - If bit is 0: value = value * 2
- * - If bit is 1: value = value * 2 - 1
- *
- * To reverse:
- * - We work backwards from the final value
- * - For each step, determine if the previous operation was *2 or *2-1
- * - If current value is even: previous was *2, so bit is 0, prev_value = value / 2
- * - If current value is odd: previous was *2-1, so bit is 1, prev_value = (value + 1) / 2
- */
- function reverseEncode(finalValue) {
- const bits = [];
- let value = finalValue;
- // Work backwards for 20 steps (bits 20 down to 1)
- for (let i = 0; i < 20; i++) {
- if (value % 2n === 0n) {
- // Even: previous operation was *2, so bit is 0
- bits.unshift(0);
- value = value / 2n;
- } else {
- // Odd: previous operation was *2-1, so bit is 1
- bits.unshift(1);
- value = (value + 1n) / 2n;
- }
- }
- // Now value should be either 2 or 3, which tells us the first bit
- if (value === 2n) {
- bits.unshift(0);
- } else if (value === 3n) {
- bits.unshift(1);
- } else {
- throw new Error('Decompression failed: invalid starting value');
- }
- return bits.join('');
- }
- function renderResult(resultObj) {
- const {
- trace,
- valueBeforeSubtract,
- valueAfterSubtract,
- fullBinaryAfter,
- coreBinaryAfter,
- coreBitsLength,
- disambiguationBit,
- compressedWithDisambLSB,
- totalLengthBits
- } = resultObj;
- let html = '';
- html += '<div class="small"><strong>Trace (step-by-step building of value before subtract)</strong></div>';
- html += '<pre class="mono">';
- html += 'Step Bit Operation Value\n';
- html += '---- --- --------- ----------------\n';
- trace.forEach(t => {
- const step = String(t.step).padEnd(4, ' ');
- const bit = String(t.bit).padEnd(3, ' ');
- const op = String(t.op).padEnd(9, ' ');
- const val = t.value;
- html += `${step} ${bit} ${op} ${val}\n`;
- });
- html += '</pre>';
- html += `<div class="result"><strong>Value before subtracting 1,048,576</strong>: ${valueBeforeSubtract}</div>`;
- html += `<div class="result"><strong>Value after subtracting 1,048,576</strong>: ${valueAfterSubtract}</div>`;
- html += '<hr />';
- html += '<div class="small"><strong>Full binary of value after subtraction</strong></div>';
- html += `<pre class="mono">${fullBinaryAfter}</pre>`;
- html += '<div class="small" style="margin-top:8px;"><strong>Compressed output (leading 1-bit stripped)</strong></div>';
- html += `<pre class="mono">${coreBinaryAfter}</pre>`;
- html += '<div class="small" style="margin-top:8px;"><strong>Disambiguation bit (LSB)</strong></div>';
- html += `<div class="mono">disambiguationBit (LSB) = <strong>${disambiguationBit}</strong> (original first input bit)</div>`;
- html += '<div style="margin-top:8px;" class="small"><strong>Final transmitted payload (core binary without leading 1 + LSB disambiguation)</strong></div>';
- html += `<pre class="mono">${compressedWithDisambLSB}</pre>`;
- html += `<div style="margin-top:8px;" class="small"><strong>Total length used for payload (for statistics)</strong></div>`;
- html += `<div class="mono"><strong>${totalLengthBits}</strong> bits (core binary digits without leading 1: ${coreBitsLength} + 1 LSB disambiguation)</div>`;
- html += '<div style="margin-top:8px;" class="small"><strong>Decompressor recipe (LSB disambiguation)</strong></div>';
- html += '<pre class="mono">';
- html += '1) Read the last bit of the received payload => disambiguation bit (LSB).\n';
- html += '2) Take the remaining characters (all but last). These represent the binary with the leading 1-bit stripped.\n';
- html += '3) Prepend "1" to restore the full binary representation of valueAfterSubtract.\n';
- html += '4) Parse the binary to get valueAfterSubtract.\n';
- html += '5) To recover the original value before subtraction, compute: valueAfterSubtract + 1,048,576.\n';
- html += '\n';
- html += 'Example (decompressor):\n';
- html += `received payload: ${compressedWithDisambLSB}\n`;
- html += `disambiguation (LSB): ${disambiguationBit}\n`;
- html += `core binary (leading 1 stripped): ${coreBinaryAfter}\n`;
- html += `restored full binary: ${fullBinaryAfter}\n`;
- html += `parsed valueAfterSubtract: ${valueAfterSubtract}\n`;
- html += `recovered valueBeforeSubtract: ${valueBeforeSubtract}\n`;
- html += '</pre>';
- outputEl.innerHTML = html;
- }
- function renderDecompressResult(decompressObj, originalBits = null) {
- const {
- disambiguationBit,
- coreBinary,
- fullBinary,
- valueAfterSubtract,
- valueBeforeSubtract,
- recovered21Bits
- } = decompressObj;
- let html = '';
- html += '<div class="small"><strong>Decompression Steps</strong></div>';
- html += '<pre class="mono">';
- html += `1) LSB disambiguation bit: ${disambiguationBit}\n`;
- html += `2) Core binary (leading 1 stripped): ${coreBinary}\n`;
- html += `3) Restored full binary: ${fullBinary}\n`;
- html += `4) Parsed valueAfterSubtract: ${valueAfterSubtract}\n`;
- html += `5) Recovered valueBeforeSubtract: ${valueBeforeSubtract}\n`;
- html += '</pre>';
- html += `<div class="result"><strong>Recovered 21-bit input</strong>: ${recovered21Bits}</div>`;
- if (originalBits) {
- if (originalBits === recovered21Bits) {
- html += '<div class="success">✓ Roundtrip successful! Original and recovered bits match.</div>';
- } else {
- html += '<div class="error">✗ Roundtrip failed! Bits do not match.</div>';
- html += `<div class="small">Original: ${originalBits}</div>`;
- html += `<div class="small">Recovered: ${recovered21Bits}</div>`;
- }
- }
- decompressOutputEl.innerHTML = html;
- }
- computeBtn.addEventListener('click', () => {
- errorEl.textContent = '';
- outputEl.innerHTML = '';
- const s = bitsInput.value.trim();
- const v = validateBits(s);
- if (v) { errorEl.textContent = v; return; }
- try {
- const result = computeFromBits(s);
- renderResult(result);
- // Auto-fill the compressed input for easy testing
- compressedInput.value = result.compressedWithDisambLSB;
- } catch (err) {
- errorEl.textContent = 'Computation error';
- console.error(err);
- }
- });
- decompressBtn.addEventListener('click', () => {
- decompressErrorEl.textContent = '';
- decompressOutputEl.innerHTML = '';
- const s = compressedInput.value.trim();
- const v = validateCompressed(s);
- if (v) { decompressErrorEl.textContent = v; return; }
- try {
- const result = decompressPayload(s);
- renderDecompressResult(result);
- } catch (err) {
- decompressErrorEl.textContent = 'Decompression error: ' + err.message;
- console.error(err);
- }
- });
- testRoundtripBtn.addEventListener('click', () => {
- errorEl.textContent = '';
- outputEl.innerHTML = '';
- decompressErrorEl.textContent = '';
- decompressOutputEl.innerHTML = '';
- // Use current bits input or generate random
- let originalBits = bitsInput.value.trim();
- if (!originalBits || originalBits.length !== 21) {
- originalBits = randomBits21();
- bitsInput.value = originalBits;
- }
- try {
- // Compress
- const compressResult = computeFromBits(originalBits);
- renderResult(compressResult);
- // Decompress
- const decompressResult = decompressPayload(compressResult.compressedWithDisambLSB);
- renderDecompressResult(decompressResult, originalBits);
- compressedInput.value = compressResult.compressedWithDisambLSB;
- } catch (err) {
- errorEl.textContent = 'Roundtrip test error: ' + err.message;
- console.error(err);
- }
- });
- exampleBtn.addEventListener('click', () => {
- bitsInput.value = '010101001110000111000';
- errorEl.textContent = '';
- outputEl.innerHTML = '';
- });
- randomBtn.addEventListener('click', () => {
- bitsInput.value = randomBits21();
- errorEl.textContent = '';
- outputEl.innerHTML = '';
- computeBtn.click();
- });
- copyBtn.addEventListener('click', async () => {
- const out = outputEl.innerText || '';
- if (!out) { errorEl.textContent = 'Nothing to copy'; return; }
- try {
- await navigator.clipboard.writeText(out);
- errorEl.textContent = 'Result copied to clipboard';
- setTimeout(() => { errorEl.textContent = ''; }, 2000);
- } catch (e) {
- errorEl.textContent = 'Copy failed';
- }
- });
- function randomBits21() {
- let s = '';
- for (let i = 0; i < 21; i++) {
- s += (Math.random() < 0.5 ? '0' : '1');
- }
- return s;
- }
- // Run many random tests and show summary
- runTestsBtn.addEventListener('click', () => {
- errorEl.textContent = '';
- outputEl.innerHTML = '';
- const count = Math.max(1, Math.min(100000, Number(countInput.value) || 100));
- // Track bit-length distribution
- const lengthCounts = new Map();
- let min = null;
- let max = null;
- let sum = 0n;
- for (let i = 0; i < count; i++) {
- const bits = randomBits21();
- const res = computeFromBits(bits);
- const valAfter = BigInt(res.valueAfterSubtract);
- if (min === null || valAfter < min) min = valAfter;
- if (max === null || valAfter > max) max = valAfter;
- sum += valAfter;
- const len = res.totalLengthBits;
- lengthCounts.set(len, (lengthCounts.get(len) || 0) + 1);
- }
- const avg = sum / BigInt(count);
- // Sort lengths for display
- const sortedLengths = Array.from(lengthCounts.keys()).sort((a, b) => a - b);
- let html = '<div class="small"><strong>Random tests summary (using post-subtract binary with leading 1-bit stripped + 1-bit LSB disambiguation)</strong></div>';
- html += '<pre>';
- html += `Total samples: ${count}\n`;
- html += `Min valueAfterSubtract: ${min.toString()}\n`;
- html += `Max valueAfterSubtract: ${max.toString()}\n`;
- html += `Average valueAfterSubtract: ${avg.toString()}\n`;
- html += '\n';
- html += '<strong>Payload Length Distribution (bits):</strong>\n';
- html += 'Bits Count Percentage\n';
- html += '---- --------- ----------\n';
- sortedLengths.forEach(len => {
- const cnt = lengthCounts.get(len);
- const pct = ((cnt / count) * 100).toFixed(2);
- const lenStr = String(len).padEnd(4, ' ');
- const cntStr = String(cnt).padStart(9, ' ');
- const pctStr = String(pct).padStart(6, ' ') + '%';
- html += `${lenStr} ${cntStr} ${pctStr}\n`;
- });
- // Calculate statistics
- const minLen = sortedLengths[0];
- const maxLen = sortedLengths[sortedLengths.length - 1];
- let weightedSum = 0;
- sortedLengths.forEach(len => {
- weightedSum += len * lengthCounts.get(len);
- });
- const avgLen = (weightedSum / count).toFixed(2);
- html += '\n';
- html += `Min payload length: ${minLen} bits\n`;
- html += `Max payload length: ${maxLen} bits\n`;
- html += `Average payload length: ${avgLen} bits\n`;
- html += `Compression ratio: ${avgLen}/21 = ${(avgLen / 21).toFixed(4)} (${((avgLen / 21) * 100).toFixed(2)}%)\n`;
- html += '</pre>';
- outputEl.innerHTML = html;
- });
- // allow Enter to compute
- bitsInput.addEventListener('keydown', (e) => {
- if (e.key === 'Enter') { computeBtn.click(); }
- });
- compressedInput.addEventListener('keydown', (e) => {
- if (e.key === 'Enter') { decompressBtn.click(); }
- });
- </script>
- </body>
- </html>
Pasted 2026-08-20 20:16:35
Short link:
Short link: