const bitsRows = document.getElementById("bitsRows"); 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 = clampInt(Number(bitsInput.value || 8), 1, 64); let bits = new Array(bitCount).fill(false); // index 0 = MSB function clampInt(n, min, max){ n = Number(n); if (!Number.isFinite(n)) n = min; n = Math.floor(n); return Math.max(min, Math.min(max, n)); } function isTwos(){ return !!modeToggle.checked; } function msbValue(){ return 2 ** (bitCount - 1); } function unsignedValueAt(i){ // i=0 is MSB return 2 ** (bitCount - 1 - i); } function computeUnsigned(){ let sum = 0; for (let i = 0; i < bitCount; i++){ if (bits[i]) sum += unsignedValueAt(i); } return sum; } function computeDenary(){ const u = computeUnsigned(); if (!isTwos()) return u; // Two's complement: // if MSB is 1, value = unsigned - 2^n if (bits[0]) return u - (2 ** bitCount); return u; } function bitsToString(){ return bits.map(b => (b ? "1" : "0")).join(""); } function updateModeHint(){ if (isTwos()){ 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."; } } function buildBitsUI(){ bitsRows.innerHTML = ""; // Build rows of 8 bits const rowCount = Math.ceil(bitCount / 8); for (let r = 0; r < rowCount; r++){ const row = document.createElement("div"); row.className = "byteRow"; const start = r * 8; const end = Math.min(start + 8, bitCount); for (let i = start; i < end; i++){ const bitEl = document.createElement("div"); bitEl.className = "bit"; // label: show -MSB in two's complement const labelVal = (isTwos() && i === 0) ? -msbValue() : unsignedValueAt(i); bitEl.innerHTML = `
${labelVal}
`; row.appendChild(bitEl); } bitsRows.appendChild(row); } // Hook switches bitsRows.querySelectorAll('input[type="checkbox"][data-index]').forEach(input => { input.addEventListener("change", () => { const i = Number(input.dataset.index); bits[i] = input.checked; updateReadout(); }); }); syncUI(); } function syncUI(){ // sync inputs bitsRows.querySelectorAll('input[type="checkbox"][data-index]').forEach(input => { const i = Number(input.dataset.index); input.checked = !!bits[i]; }); // sync bulbs for (let i = 0; i < bitCount; i++){ const bulb = document.getElementById(`bulb-${i}`); if (bulb) bulb.classList.toggle("on", !!bits[i]); } updateReadout(); } function updateReadout(){ denaryEl.textContent = String(computeDenary()); binaryEl.textContent = bitsToString(); } function setFromBinary(bin){ const clean = String(bin).replace(/\s+/g, ""); if (!/^[01]+$/.test(clean)) return false; const padded = clean.slice(-bitCount).padStart(bitCount, "0"); for (let i = 0; i < bitCount; i++){ bits[i] = padded[i] === "1"; } syncUI(); return true; } function setFromDenary(n){ n = Number(n); if (!Number.isInteger(n)) return false; if (!isTwos()){ // unsigned: 0 .. (2^n - 1) const max = (2 ** bitCount) - 1; if (n < 0 || n > max) return false; for (let i = 0; i < bitCount; i++){ const v = unsignedValueAt(i); if (n >= v){ bits[i] = true; n -= v; } else { bits[i] = false; } } syncUI(); return true; } // two's complement: -(2^(n-1)) .. (2^(n-1)-1) const min = -(2 ** (bitCount - 1)); const max = (2 ** (bitCount - 1)) - 1; if (n < min || n > max) return false; // convert to unsigned representation let u = n; if (u < 0) u = (2 ** bitCount) + u; // wrap for (let i = 0; i < bitCount; i++){ const v = unsignedValueAt(i); if (u >= v){ bits[i] = true; u -= v; } else { bits[i] = false; } } syncUI(); return true; } function shiftLeft(){ bits.shift(); // drop MSB bits.push(false); // add LSB syncUI(); } function shiftRight(){ bits.pop(); // drop LSB bits.unshift(false); // add MSB syncUI(); } function setBitCount(newCount){ newCount = clampInt(newCount, 4, 64); if (newCount === bitCount) return; // preserve right-most bits (LSB side) when resizing const old = bits.slice(); const next = new Array(newCount).fill(false); const copy = Math.min(old.length, next.length); for (let k = 0; k < copy; k++){ // copy from end (LSB) next[newCount - 1 - k] = old[old.length - 1 - k]; } bitCount = newCount; bits = next; bitsInput.value = String(bitCount); buildBitsUI(); } /* -------------------- events -------------------- */ modeToggle.addEventListener("change", () => { updateModeHint(); buildBitsUI(); // rebuild labels (MSB becomes negative/positive) }); btnBitsUp.addEventListener("click", () => setBitCount(bitCount + 1)); btnBitsDown.addEventListener("click", () => setBitCount(bitCount - 1)); bitsInput.addEventListener("change", () => setBitCount(Number(bitsInput.value))); btnShiftLeft.addEventListener("click", shiftLeft); btnShiftRight.addEventListener("click", shiftRight); btnCustomBinary.addEventListener("click", () => { const v = prompt(`Enter ${bitCount}-bit binary:`); if (v === null) return; if (!setFromBinary(v)) alert("Invalid binary. Use only 0 and 1."); }); btnCustomDenary.addEventListener("click", () => { const min = isTwos() ? -(2 ** (bitCount - 1)) : 0; const max = isTwos() ? (2 ** (bitCount - 1)) - 1 : (2 ** bitCount) - 1; const v = prompt(`Enter a denary number (${min} to ${max}):`); if (v === null) return; if (!setFromDenary(Number(v))) alert("Invalid denary for current mode/bit width."); }); /* -------------------- init -------------------- */ bitsInput.value = String(bitCount); updateModeHint(); buildBitsUI();