Below is a live CRC calculator. Type any text and watch its checksum appear. Then click any bit in the binary view to flip it — exactly the kind of single-bit error a noisy channel produces — and watch the CRC change completely.
<p class="hint">Type a message. Its bits become a polynomial, divided by a fixed generator; the <b>remainder</b> is the CRC. Click any bit below to <b>flip</b> it (a single-bit error) and watch the CRC change — that is how corruption is caught.</p>
<label class="lbl">Message</label>
<input id="msg" type="text" value="HELLO" maxlength="12" />
<label class="lbl">Bits (click one to flip it)</label>
<div id="bits" class="bits"></div>
<div class="crc">
<span>CRC-8 =</span>
<b id="crc">--</b>
<span id="state" class="state"></span>
</div>
<div class="btns">
<button id="reset" type="button" class="ghost">Restore message</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.lbl { display: block; font-size: .78rem; font-weight: 700; text-transform: uppercase;
letter-spacing: .04em; color: #5a7088; margin: .6rem 0 .25rem; }
#msg { font: 600 16px ui-monospace, monospace; padding: .45rem .6rem; width: 100%;
border: 1px solid #cdd9e3; border-radius: 8px; }
.bits { display: flex; flex-wrap: wrap; gap: 4px; margin: .2rem 0 .4rem; }
.bit { width: 26px; height: 30px; display: flex; align-items: center; justify-content: center;
font: 700 14px ui-monospace, monospace; border-radius: 6px; cursor: pointer;
background: #e8eef3; color: #1d3557; border: 1px solid #cdd9e3; transition: all .1s; user-select: none; }
.bit:hover { background: #d6e2ec; }
.bit.one { background: #1d3557; color: #fff; border-color: #16283f; }
.bit.flipped { outline: 2px solid #e63946; outline-offset: 1px; }
.crc { margin: .6rem 0; font-size: 1.05rem; }
.crc b { font: 800 1.2rem ui-monospace, monospace; color: #1d3557; }
.state { margin-left: .6rem; font-weight: 700; font-size: .9rem; }
.state.changed { color: #c92f3c; }
.state.match { color: #0a7d33; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
// CRC-8 (polynomial 0x07, the CRC-8/SMBus standard). We treat the message bits
// as a polynomial over GF(2) and take the remainder modulo the generator. The
// math is pure shift + XOR: addition in GF(2) is XOR, so there are no carries.
const POLY = 0x07; // x^8 + x^2 + x + 1 (the low 8 bits of the divisor)
function crc8(bytes){
let crc = 0;
for (const b of bytes){
crc ^= b; // bring in the next byte
for (let i = 0; i < 8; i++){
// if the top bit is set, shift and XOR with the generator; else just shift
crc = (crc & 0x80) ? ((crc << 1) ^ POLY) & 0xff : (crc << 1) & 0xff;
}
}
return crc;
}
const hex2 = n => '0x' + n.toString(16).toUpperCase().padStart(2,'0');
let bytes = []; // current (possibly bit-flipped) bytes
let original = []; // bytes straight from the text box
let baseline = 0; // CRC of the untouched message
function fromText(t){
const arr = [];
for (let i = 0; i < t.length; i++) arr.push(t.charCodeAt(i) & 0xff);
return arr.length ? arr : [0];
}
function renderBits(){
const box = document.getElementById('bits'); box.innerHTML = '';
bytes.forEach((byte, bi) => {
for (let bit = 7; bit >= 0; bit--){
const on = (byte >> bit) & 1;
const flipped = ((original[bi] ?? 0) >> bit & 1) !== on;
const d = document.createElement('div');
d.className = 'bit' + (on ? ' one' : '') + (flipped ? ' flipped' : '');
d.textContent = on;
d.addEventListener('click', () => { bytes[bi] ^= (1 << bit); update(); });
box.appendChild(d);
}
});
}
function update(){
renderBits();
const c = crc8(bytes);
document.getElementById('crc').textContent = hex2(c);
const st = document.getElementById('state');
const changed = bytes.some((b,i) => b !== original[i]) || bytes.length !== original.length;
if (!changed){ st.textContent = '(matches the original message)'; st.className = 'state match'; }
else { st.textContent = (c === baseline ? '(undetected!)' : 'error detected — CRC differs'); st.className = 'state changed'; }
}
function load(){
original = fromText(document.getElementById('msg').value);
bytes = original.slice();
baseline = crc8(original);
update();
}
document.getElementById('msg').addEventListener('input', load);
document.getElementById('reset').addEventListener('click', () => {
bytes = original.slice(); update();
});
load();
<p class="hint">Type a message. Its bits become a polynomial, divided by a fixed generator; the <b>remainder</b> is the CRC. Click any bit below to <b>flip</b> it (a single-bit error) and watch the CRC change — that is how corruption is caught.</p>
<label class="lbl">Message</label>
<input id="msg" type="text" value="HELLO" maxlength="12" />
<label class="lbl">Bits (click one to flip it)</label>
<div id="bits" class="bits"></div>
<div class="crc">
<span>CRC-8 =</span>
<b id="crc">--</b>
<span id="state" class="state"></span>
</div>
<div class="btns">
<button id="reset" type="button" class="ghost">Restore message</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.lbl { display: block; font-size: .78rem; font-weight: 700; text-transform: uppercase;
letter-spacing: .04em; color: #5a7088; margin: .6rem 0 .25rem; }
#msg { font: 600 16px ui-monospace, monospace; padding: .45rem .6rem; width: 100%;
border: 1px solid #cdd9e3; border-radius: 8px; }
.bits { display: flex; flex-wrap: wrap; gap: 4px; margin: .2rem 0 .4rem; }
.bit { width: 26px; height: 30px; display: flex; align-items: center; justify-content: center;
font: 700 14px ui-monospace, monospace; border-radius: 6px; cursor: pointer;
background: #e8eef3; color: #1d3557; border: 1px solid #cdd9e3; transition: all .1s; user-select: none; }
.bit:hover { background: #d6e2ec; }
.bit.one { background: #1d3557; color: #fff; border-color: #16283f; }
.bit.flipped { outline: 2px solid #e63946; outline-offset: 1px; }
.crc { margin: .6rem 0; font-size: 1.05rem; }
.crc b { font: 800 1.2rem ui-monospace, monospace; color: #1d3557; }
.state { margin-left: .6rem; font-weight: 700; font-size: .9rem; }
.state.changed { color: #c92f3c; }
.state.match { color: #0a7d33; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
// CRC-8 (polynomial 0x07, the CRC-8/SMBus standard). We treat the message bits
// as a polynomial over GF(2) and take the remainder modulo the generator. The
// math is pure shift + XOR: addition in GF(2) is XOR, so there are no carries.
const POLY = 0x07; // x^8 + x^2 + x + 1 (the low 8 bits of the divisor)
function crc8(bytes){
let crc = 0;
for (const b of bytes){
crc ^= b; // bring in the next byte
for (let i = 0; i < 8; i++){
// if the top bit is set, shift and XOR with the generator; else just shift
crc = (crc & 0x80) ? ((crc << 1) ^ POLY) & 0xff : (crc << 1) & 0xff;
}
}
return crc;
}
const hex2 = n => '0x' + n.toString(16).toUpperCase().padStart(2,'0');
let bytes = []; // current (possibly bit-flipped) bytes
let original = []; // bytes straight from the text box
let baseline = 0; // CRC of the untouched message
function fromText(t){
const arr = [];
for (let i = 0; i < t.length; i++) arr.push(t.charCodeAt(i) & 0xff);
return arr.length ? arr : [0];
}
function renderBits(){
const box = document.getElementById('bits'); box.innerHTML = '';
bytes.forEach((byte, bi) => {
for (let bit = 7; bit >= 0; bit--){
const on = (byte >> bit) & 1;
const flipped = ((original[bi] ?? 0) >> bit & 1) !== on;
const d = document.createElement('div');
d.className = 'bit' + (on ? ' one' : '') + (flipped ? ' flipped' : '');
d.textContent = on;
d.addEventListener('click', () => { bytes[bi] ^= (1 << bit); update(); });
box.appendChild(d);
}
});
}
function update(){
renderBits();
const c = crc8(bytes);
document.getElementById('crc').textContent = hex2(c);
const st = document.getElementById('state');
const changed = bytes.some((b,i) => b !== original[i]) || bytes.length !== original.length;
if (!changed){ st.textContent = '(matches the original message)'; st.className = 'state match'; }
else { st.textContent = (c === baseline ? '(undetected!)' : 'error detected — CRC differs'); st.className = 'state changed'; }
}
function load(){
original = fromText(document.getElementById('msg').value);
bytes = original.slice();
baseline = crc8(original);
update();
}
document.getElementById('msg').addEventListener('input', load);
document.getElementById('reset').addEventListener('click', () => {
bytes = original.slice(); update();
});
load();
Notice two things. First, flipping any single bit always changes the CRC, so the error is caught. Second, the whole computation is just shifting and XOR-ing — no multiplication, no division in the usual sense, just the bitwise arithmetic of polynomials over the field with two elements.
Comments
Loading comments...