You've already forked computing-box
274 lines
7.3 KiB
JavaScript
274 lines
7.3 KiB
JavaScript
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 = `
|
||
<div class="bulb" id="bulb-${i}" aria-hidden="true">💡</div>
|
||
<div class="bitVal num">${placeValue}</div>
|
||
<label class="switch" aria-label="Toggle bit ${placeValue}">
|
||
<input type="checkbox" data-index="${i}">
|
||
<span class="slider"></span>
|
||
</label>
|
||
`;
|
||
|
||
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();
|