const bitsGrid = document.getElementById("bitsGrid"); const denaryEl = document.getElementById("denaryNumber"); const binaryEl = document.getElementById("binaryNumber"); const modeToggle = document.getElementById("modeToggle"); const modeHint = document.getElementById("modeHint"); const bitsInput = document.getElementById("bitsInput"); const btnBitsUp = document.getElementById("btnBitsUp"); const btnBitsDown = document.getElementById("btnBitsDown"); const btnShiftLeft = document.getElementById("btnShiftLeft"); const btnShiftRight = document.getElementById("btnShiftRight"); const btnCustomBinary = document.getElementById("btnCustomBinary"); const btnCustomDenary = document.getElementById("btnCustomDenary"); let bitCount = 8; // 4..64 let mode = "unsigned"; // "unsigned" | "twos" // bits[0] is MSB, bits[bitCount-1] is LSB let bits = new Array(bitCount).fill(false); /* ----------------------------- Helpers ----------------------------- */ function clampInt(n, min, max) { n = Number(n); if (!Number.isFinite(n)) return min; return Math.max(min, Math.min(max, Math.trunc(n))); } function maxUnsigned(nBits) { return (2 ** nBits) - 1; } function minTwos(nBits) { return -(2 ** (nBits - 1)); } function maxTwos(nBits) { return (2 ** (nBits - 1)) - 1; } function bitsToUnsigned() { // MSB..LSB let v = 0; for (let i = 0; i < bitCount; i++) { if (bits[i]) v += 2 ** (bitCount - 1 - i); } return v; } function bitsToSigned() { const u = bitsToUnsigned(); if (mode !== "twos") return u; // if MSB is 1 => negative if (!bits[0]) return u; return u - (2 ** bitCount); } function setBitsFromUnsigned(u) { u = clampInt(u, 0, maxUnsigned(bitCount)); for (let i = 0; i < bitCount; i++) { const place = 2 ** (bitCount - 1 - i); bits[i] = u >= place; if (bits[i]) u -= place; } } function setBitsFromSigned(s) { // convert signed -> unsigned representation const min = minTwos(bitCount); const max = maxTwos(bitCount); s = clampInt(s, min, max); const u = s < 0 ? (2 ** bitCount) + s : s; setBitsFromUnsigned(u); } /* ----------------------------- Build UI ----------------------------- */ function buildBitsUI() { bitsGrid.innerHTML = ""; // Force wrap every 8 bits visually // CSS already does repeat(8,...). Nothing else needed. for (let i = 0; i < bitCount; i++) { const placeValue = 2 ** (bitCount - 1 - i); const bit = document.createElement("div"); bit.className = "bit"; bit.innerHTML = `
${placeValue}
`; bitsGrid.appendChild(bit); } // Hook switches bitsGrid.querySelectorAll('input[type="checkbox"][data-index]').forEach((input) => { input.addEventListener("change", () => { const i = Number(input.dataset.index); bits[i] = input.checked; updateReadout(); }); }); syncUI(); } /* ----------------------------- Sync + Display ----------------------------- */ function syncUI() { bitsGrid.querySelectorAll('input[type="checkbox"][data-index]').forEach((input) => { const i = Number(input.dataset.index); input.checked = !!bits[i]; const bulb = document.getElementById(`bulb-${i}`); if (bulb) bulb.classList.toggle("on", !!bits[i]); }); updateReadout(); } function updateReadout() { const binaryStr = bits.map((b) => (b ? "1" : "0")).join(""); binaryEl.textContent = binaryStr; const d = bitsToSigned(); denaryEl.textContent = String(d); // hint text if (mode === "twos") { modeHint.textContent = "Tip: In two’s complement, the left-most bit (MSB) represents a negative value."; } else { modeHint.textContent = "Tip: In unsigned binary, all bits represent positive values."; } // bulbs for (let i = 0; i < bitCount; i++) { const bulb = document.getElementById(`bulb-${i}`); if (bulb) bulb.classList.toggle("on", !!bits[i]); } } /* ----------------------------- Input setters ----------------------------- */ function setFromBinary(bin) { const clean = String(bin).replace(/\s+/g, ""); if (!/^[01]+$/.test(clean)) return false; const padded = clean.slice(-bitCount).padStart(bitCount, "0"); bits = [...padded].map((ch) => ch === "1"); syncUI(); return true; } function setFromDenary(n) { if (!Number.isInteger(n)) return false; if (mode === "twos") { if (n < minTwos(bitCount) || n > maxTwos(bitCount)) return false; setBitsFromSigned(n); } else { if (n < 0 || n > maxUnsigned(bitCount)) return false; setBitsFromUnsigned(n); } syncUI(); return true; } /* ----------------------------- Shifts ----------------------------- */ function shiftLeft() { // left shift: drop MSB, append 0 bits.shift(); bits.push(false); syncUI(); } function shiftRight() { // right shift: // unsigned = logical (prepend 0) // twos = arithmetic (preserve MSB) const msb = bits[0]; bits.pop(); bits.unshift(mode === "twos" ? msb : false); syncUI(); } /* ----------------------------- Mode + Bit width ----------------------------- */ function setMode(nextMode) { if (nextMode !== "unsigned" && nextMode !== "twos") return; // keep the *bit pattern* the same, just interpret differently mode = nextMode; updateReadout(); } function setBitCount(nextCount) { nextCount = clampInt(nextCount, 4, 64); if (nextCount === bitCount) return; // preserve current binary string as much as possible (keep least significant bits) const currentBinary = bits.map((b) => (b ? "1" : "0")).join(""); bitCount = nextCount; bitsInput.value = String(bitCount); bits = new Array(bitCount).fill(false); buildBitsUI(); setFromBinary(currentBinary); // reapply into new width (LSB-preserving) } /* ----------------------------- Wire up controls ----------------------------- */ modeToggle.addEventListener("change", () => { setMode(modeToggle.checked ? "twos" : "unsigned"); }); btnBitsUp.addEventListener("click", () => setBitCount(bitCount + 1)); btnBitsDown.addEventListener("click", () => setBitCount(bitCount - 1)); bitsInput.addEventListener("change", () => setBitCount(bitsInput.value)); bitsInput.addEventListener("keydown", (e) => { if (e.key === "Enter") setBitCount(bitsInput.value); }); btnShiftLeft.addEventListener("click", shiftLeft); btnShiftRight.addEventListener("click", shiftRight); btnCustomBinary.addEventListener("click", () => { const v = prompt(`Enter a ${bitCount}-bit binary number:`); if (v === null) return; if (!setFromBinary(v)) alert("Invalid binary. Use only 0 and 1."); }); btnCustomDenary.addEventListener("click", () => { const min = mode === "twos" ? minTwos(bitCount) : 0; const max = mode === "twos" ? maxTwos(bitCount) : maxUnsigned(bitCount); const v = prompt(`Enter a denary number (${min} to ${max}):`); if (v === null) return; const n = Number(v); if (!Number.isInteger(n) || !setFromDenary(n)) { alert(`Invalid denary. Enter an integer from ${min} to ${max}.`); } }); /* ----------------------------- Init ----------------------------- */ bitsInput.value = String(bitCount); modeToggle.checked = false; // unsigned by default buildBitsUI(); updateReadout();