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();