/* Binary simulator for Computing:Box
- Wrap bits every 8 (CSS handles layout)
- Bit width 4..64
- Unsigned + Two’s complement toggle (WORKING)
- Bulbs + toggle switches for each bit
*/
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 = clampInt(Number(bitsInput?.value ?? 8), 4, 64);
let isTwos = false;
// bits[0] is MSB, bits[bitCount-1] is LSB
let bits = new Array(bitCount).fill(false);
function clampInt(n, min, max) {
n = Number(n);
if (!Number.isInteger(n)) n = min;
return Math.max(min, Math.min(max, n));
}
function pow2(exp) {
// safe up to 2^63 in JS integer precision? (JS uses float) but our usage is display/control, ok.
return 2 ** exp;
}
/* -----------------------------
Build UI (bulbs + switches)
----------------------------- */
function buildBits(count) {
bitCount = clampInt(count, 4, 64);
bits = resizeBits(bits, bitCount);
bitsGrid.innerHTML = "";
for (let i = 0; i < bitCount; i++) {
const exp = bitCount - 1 - i; // MSB has highest exponent
const value = pow2(exp);
const bit = document.createElement("div");
bit.className = "bit";
bit.innerHTML = `
${value}
`;
bitsGrid.appendChild(bit);
}
hookSwitches();
syncUI();
}
function resizeBits(oldBits, newCount) {
// keep LSB end stable when changing bit width:
// align old bits to the right (LSB)
const out = new Array(newCount).fill(false);
const copy = Math.min(oldBits.length, newCount);
for (let k = 0; k < copy; k++) {
// copy from end (LSB)
out[newCount - 1 - k] = oldBits[oldBits.length - 1 - k];
}
return out;
}
function hookSwitches() {
bitsGrid.querySelectorAll('input[type="checkbox"][data-index]').forEach((input) => {
input.addEventListener("change", () => {
const i = Number(input.dataset.index);
bits[i] = input.checked;
updateReadout();
updateBulb(i);
});
});
}
/* -----------------------------
Mode toggle (Unsigned <-> Two’s)
----------------------------- */
function setModeTwos(on) {
isTwos = !!on;
modeHint.textContent = isTwos
? "Tip: In two’s complement, the left-most bit (MSB) represents a negative value."
: "Tip: In unsigned binary, all bits represent positive values.";
// Just re-calc denary using current bit pattern
updateReadout();
}
modeToggle?.addEventListener("change", () => setModeTwos(modeToggle.checked));
/* -----------------------------
Calculations
----------------------------- */
function getUnsignedValue() {
let n = 0;
for (let i = 0; i < bitCount; i++) {
if (!bits[i]) continue;
const exp = bitCount - 1 - i;
n += pow2(exp);
}
return n;
}
function getTwosValue() {
// MSB has negative weight
let n = 0;
for (let i = 0; i < bitCount; i++) {
if (!bits[i]) continue;
const exp = bitCount - 1 - i;
if (i === 0) n -= pow2(exp); // MSB
else n += pow2(exp);
}
return n;
}
function getDenary() {
return isTwos ? getTwosValue() : getUnsignedValue();
}
function getBinaryString() {
return bits.map(b => (b ? "1" : "0")).join("");
}
/* -----------------------------
UI updates
----------------------------- */
function updateBulb(i) {
const bulb = document.getElementById(`bulb-${i}`);
if (bulb) bulb.classList.toggle("on", bits[i]);
}
function updateReadout() {
denaryEl.textContent = String(getDenary());
binaryEl.textContent = getBinaryString();
}
function syncUI() {
bitsGrid.querySelectorAll('input[type="checkbox"][data-index]').forEach((input) => {
const i = Number(input.dataset.index);
input.checked = !!bits[i];
updateBulb(i);
});
updateReadout();
}
/* -----------------------------
Set from Binary / Denary
----------------------------- */
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 (!isTwos) {
const min = 0;
const max = pow2(bitCount) - 1;
if (n < min || n > max) return false;
// unsigned fill from MSB->LSB
let remaining = n;
bits = bits.map((_, i) => {
const exp = bitCount - 1 - i;
const value = pow2(exp);
if (remaining >= value) {
remaining -= value;
return true;
}
return false;
});
syncUI();
return true;
}
// Two's complement bounds
const min = -pow2(bitCount - 1);
const max = pow2(bitCount - 1) - 1;
if (n < min || n > max) return false;
// Convert to raw unsigned representation:
// if negative, represent as 2^bitCount + n
let raw = n;
if (raw < 0) raw = pow2(bitCount) + raw;
let remaining = raw;
bits = bits.map((_, i) => {
const exp = bitCount - 1 - i;
const value = pow2(exp);
if (remaining >= value) {
remaining -= value;
return true;
}
return false;
});
syncUI();
return true;
}
/* -----------------------------
Shifts
----------------------------- */
function shiftLeft() {
// drop MSB, append 0 at LSB
bits.shift();
bits.push(false);
syncUI();
}
function shiftRight() {
// unsigned: logical shift right (prepend 0)
// twos: arithmetic shift right (prepend old MSB)
const msb = bits[0];
bits.pop();
bits.unshift(isTwos ? msb : false);
syncUI();
}
/* -----------------------------
Bit width controls
----------------------------- */
function applyBitCount(next) {
const v = clampInt(next, 4, 64);
bitsInput.value = String(v);
buildBits(v);
}
btnBitsUp?.addEventListener("click", () => applyBitCount(bitCount + 1));
btnBitsDown?.addEventListener("click", () => applyBitCount(bitCount - 1));
bitsInput?.addEventListener("change", () => {
applyBitCount(Number(bitsInput.value));
});
/* -----------------------------
Buttons
----------------------------- */
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 input. Use only 0 and 1.");
});
btnCustomDenary?.addEventListener("click", () => {
const min = isTwos ? -pow2(bitCount - 1) : 0;
const max = isTwos ? (pow2(bitCount - 1) - 1) : (pow2(bitCount) - 1);
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 input. Enter an integer from ${min} to ${max}.`);
}
});
/* -----------------------------
INIT
----------------------------- */
function init() {
// default mode: unsigned
setModeTwos(false);
modeToggle.checked = false;
// build initial bits
applyBitCount(bitCount);
}
init();