Files
computing-box/public/scripts/binary.js
Alexander Lyall 512a5ff8ad Tweaks to Binary UI
Signed-off-by: Alexander Lyall <alex@adcm.uk>
2025-12-14 20:00:01 +00:00

274 lines
7.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 twos 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();