From adece75b051d22bc13054978e66f9f30b3e9f8d6 Mon Sep 17 00:00:00 2001 From: David Timber Date: Wed, 2 Apr 2025 23:56:15 +0200 Subject: Add linux-casual/mp-int --- linux-casual/mp-int/anim.css | 5 + linux-casual/mp-int/animation-skel.svg | 1775 ++++++++++++++++++++++++++++++++ linux-casual/mp-int/index.css | 16 + linux-casual/mp-int/index.html | 65 ++ linux-casual/mp-int/index.js | 483 +++++++++ linux-casual/mp-int/the-math.svg | 556 ++++++++++ 6 files changed, 2900 insertions(+) create mode 100644 linux-casual/mp-int/anim.css create mode 100755 linux-casual/mp-int/animation-skel.svg create mode 100644 linux-casual/mp-int/index.css create mode 100644 linux-casual/mp-int/index.html create mode 100644 linux-casual/mp-int/index.js create mode 100755 linux-casual/mp-int/the-math.svg diff --git a/linux-casual/mp-int/anim.css b/linux-casual/mp-int/anim.css new file mode 100644 index 0000000..0f6c53b --- /dev/null +++ b/linux-casual/mp-int/anim.css @@ -0,0 +1,5 @@ +@keyframes state-change { + from { + fill: orange; + } +} diff --git a/linux-casual/mp-int/animation-skel.svg b/linux-casual/mp-int/animation-skel.svg new file mode 100755 index 0000000..7cccd50 --- /dev/null +++ b/linux-casual/mp-int/animation-skel.svg @@ -0,0 +1,1775 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (flick me) + + + 0 + + + + + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + + + + 0 + + + + 0 + + + + 0 + + + + 0 + + + + 0 + + + + 0 + + + + 0 + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + B + Co + Ci + S + + + + A + B + Co + Ci + S + + + + A + B + Co + Ci + S + + + + A + B + Co + Ci + S + + + + A + B + Co + Ci + S + + + + A + B + Co + Ci + S + + + + A + B + Co + Ci + S + + + + A + B + Co + Ci + S + + + + + + 0 + + + + 0 + + + + 0 + + + + 0 + + + + 0 + + + + 0 + + + + 0 + + + + 0 + + + + + + 0 + + + + 0 + + + + 0 + + + + 0 + + + + 0 + + + + 0 + + + + 0 + + + + 0 + + + diff --git a/linux-casual/mp-int/index.css b/linux-casual/mp-int/index.css new file mode 100644 index 0000000..b1bf0f0 --- /dev/null +++ b/linux-casual/mp-int/index.css @@ -0,0 +1,16 @@ +fieldset { + display: inline-block; +} + +.svg-container { + text-align: center; +} + +fieldset { + font-family: monospace; +} + +.ovf { + color: red; + opacity: 0; +} diff --git a/linux-casual/mp-int/index.html b/linux-casual/mp-int/index.html new file mode 100644 index 0000000..6c6311a --- /dev/null +++ b/linux-casual/mp-int/index.html @@ -0,0 +1,65 @@ + + + + Multiprecision integer arithmetics, animated + + + + + +

+ +

+

+ +

+ +
+

Programmer

+ +
+
+ OP +
+ + +
+
+ + +
+
+ +
+ Operand +
+ + + 🛑 error +
+
+ + + 🛑 error +
+
+ +
+ ACT +
+ +
+ +

+

+ Animations + +
+ +
+ +
+
+ + + diff --git a/linux-casual/mp-int/index.js b/linux-casual/mp-int/index.js new file mode 100644 index 0000000..5d25e8e --- /dev/null +++ b/linux-casual/mp-int/index.js @@ -0,0 +1,483 @@ +const BITS = 8; +const INTMAX = (() => { + let ret = 0; + + for (let i = 0; i < BITS; i += 1) { + ret = (ret << 1) | 1 + } + + return ret; +})(); +const STATE_DELAY_DEFAULT = 250; +const ANIMATIONS_DEFAULT = 'state-change 0.5s ease-in 0s 1'; +let state_delay, animations; + +function myParseInt (str) { + let v, radix; + + if (str.startsWith("0b")) { + v = str.substring(2); + radix = 2; + } + else if (str.startsWith("0x")) { + v = str.substring(2); + radix = 16; + } + else if (str.startsWith("0")) { + v = str.substring(1); + radix = 1; + } + else { + v = str; + radix = 10; + } + + if (v === '') { + return 0; + } + + return parseInt(v, radix); +} + +function onOperandChange (val, span) { + if (val.trim() === '') { + span.style.opacity = 0; + } + else { + const n = myParseInt(val); + + if (!(n <= INTMAX)) { // NaN is also handled with this single conditional + span.style.opacity = 1; + } + else { + span.style.opacity = 0; + } + } +} + +function restartClassAnimation (element) { + const evtOpts = { "once": true }; + + // I fucking hate modern web development + + element.querySelectorAll('.state').forEach((element) => { + element.style.animation = ''; + }); + element.querySelectorAll('.state-op tspan').forEach((element) => { + element.style.animation = ''; + }); + element.querySelectorAll('.state').forEach((element) => { + element.style.animation = animations; + element.addEventListener('animationend', () => { + element.style.animation = ''; + }, evtOpts); + element.addEventListener('animationcancel', () => { + element.style.animation = ''; + }, evtOpts); + }); + element.querySelectorAll('.state-op tspan').forEach((element) => { + element.style.animation = animations; + element.addEventListener('animationend', () => { + element.style.animation = ''; + }, evtOpts); + element.addEventListener('animationcancel', () => { + element.style.animation = ''; + }, evtOpts); + }); +} + +class Renderer { + reset () {} + start (opr_a, opr_b, carry) {} +} +class MathRenderer extends Renderer {} +class AdderRenderer extends Renderer {} + +class State { + timer; + outputList = []; + + addOutput (output_f) { + this.outputList.push(output_f); + } + + clearTimer () { + if (this.timer) { + clearTimeout(this.timer); + } + } + + startTimer (v, ms) { + this.clearTimer(); + this.timer = setTimeout(() => { + this.outputList.forEach((output_f) => { + output_f(v) + }); + }, ms); + } + + detach () { + this.clearTimer(); + this.outputList = null; + } +} + +class Register extends State { + #elements; + #txts = []; + #bit = false; + #onclick = null; + + constructor (elements, clickable = true) { + super(); + const self = this; + + this.#elements = elements; + + this.#elements.forEach((e) => { + const t = e.querySelector('text tspan'); + + this.#txts.push(t); + t.innerHTML = '0'; + }); + + + if (clickable) { + this.#onclick = () => { + self.value = !self.value; + }; + + this.#elements.forEach((e) => { + e.style.cursor = 'pointer'; + e.addEventListener('click', this.#onclick); + }); + } + } + + get value () { + return this.#bit; + } + + set value (v_in) { + const v = !!v_in; + + if (this.#bit == v) { + return; + } + this.#bit = v; + + this.#elements.forEach((e) => { + restartClassAnimation(e); + }); + this.#txts.forEach((e) => { + if (v) { + e.innerHTML = '1'; + } + else { + e.innerHTML = '0'; + } + }); + + this.startTimer(v, state_delay); + } + + detach () { + super.detach(); + + if (this.#onclick) { + this.#elements.forEach((e) => { + e.removeEventListener('click', this.#onclick); + }); + + this.#onclick = null; + } + + this.#elements = null; + this.#txts = null; + } +} + +class Adder extends State { + #a = false; + #b = false; + #c = false; + + #update () { + this.startTimer(this.value, state_delay); + } + + set a (v_in) { + this.#a = !!v_in; + this.#update(); + } + + set b (v_in) { + this.#b = !!v_in; + this.#update(); + } + + set c (v_in) { + this.#c = !!v_in; + this.#update(); + } + + get value () { + let sum = 0; + + if (this.#a) { + sum += 1; + } + if (this.#b) { + sum += 1; + } + if (this.#c) { + sum += 1; + } + + return { + "a": this.#a, + "b": this.#b, + "ci": this.#c, + "co": (sum & 2) > 0, + "s": (sum & 1) > 0 + }; + } +} + +function forEachBit (n, nb_bits, f) { + let bit; + + for (let i = 0; i < nb_bits; i += 1) { + bit = n & 1; + f(bit > 0); + + n = n >> 1; + } +} + +function getLocalstorageData () { + const item = localStorage.getItem('animations'); + + if (item) { + return JSON.parse(item); + } + else { + return { + "state-delay": STATE_DELAY_DEFAULT, + "animations": ANIMATIONS_DEFAULT + }; + } +} + +function setLocalstorageData (obj) { + localStorage.setItem('animations', JSON.stringify(obj)); +} + +function clearLocalstorageData () { + localStorage.removeItem('animations'); +} + +function uploadLocalstorageData (elements) { + state_delay = elements["state-delay"].value; + animations = elements["animations"].value; + setLocalstorageData({ + "state-delay": elements["state-delay"].value, + "animations": elements["animations"].value + }); +} + +function downloadLocalstorageData (elements) { + const obj = getLocalstorageData(); + + state_delay = elements["state-delay"].value = obj["state-delay"]; + animations = elements["animations"].value = obj["animations"]; +} + +window.addEventListener('load', async () => { + const svg_math = document.getElementById("svg_the-math"); + const svg_adder = document.getElementById("svg_adder"); + const span_operand_a = document.getElementById("span_operand-a"); + const span_operand_b = document.getElementById("span_operand-b"); + const css_anim = await (await fetch('./anim.css')).text(); + + // inject animation + svg_math.contentDocument.getElementById('style1').innerHTML = css_anim; + svg_adder.contentDocument.getElementById('style1').innerHTML = css_anim; + + let stateList = []; + let adders; + let registerCarry; + let registerA; + let registerB; + let registerS; + let carryState; + + function doReset () { + stateList.forEach((s) => { + s.detach(); + }); + stateList = []; + adders = []; + registerCarry = []; + registerA = []; + registerB = []; + registerS = []; + carryState = null; + carry0 = null; + + // carryState + (() => { + const eMath = svg_math.contentDocument.getElementById('carry-ovf'); + const eAdder = svg_adder.contentDocument.getElementById('ovf'); + + carryState = new Register([ eMath, eAdder ]); + stateList.push(carryState); + })(); + + // registerS + for (let i = 0; i < BITS; i += 1) { + const eMath = svg_math.contentDocument.getElementById('result-' + i); + const eAdder = svg_adder.contentDocument.getElementById('8-bit-register-c-' + i); + const r = new Register([ eMath, eAdder ], false); + + stateList.push(r); + registerS.push(r); + } + + // registerCarry + for (let i = 1; i <= BITS - 1; i += 1) { + const eMath = svg_math.contentDocument.getElementById('carry-' + i); + const eAdder = svg_adder.contentDocument.getElementById('carry-' + (i - 1)); + const r = new Register([ eMath, eAdder ], false); + + stateList.push(r); + registerCarry.push(r); + } + registerCarry.push(carryState); + + // carry0 + const carry0element = svg_math.contentDocument.getElementById('carry-0'); + carry0 = new Register([ carry0element ]); + carry0.addOutput((v) => { + adders[0].c = v; + }); + stateList.push(carry0); + + // adders + for (let i = 0; i < BITS; i += 1) { + const a = new Adder(); + + a.addOutput((v) => { + registerS[i].value = v['s']; + registerCarry[i].value = v['co']; + }); + adders.push(a); + stateList.push(a); + } + for (let i = 1; i < BITS; i += 1) { + adders[i - 1].addOutput((v) => { + adders[i].c = v['co']; + }); + } + + // registerA + for (let i = 0; i < BITS; i += 1) { + const eMath = svg_math.contentDocument.getElementById('operand-a-' + i); + const eAdder = svg_adder.contentDocument.getElementById('8-bit-register-a-' + i); + const r = new Register([ eMath, eAdder ]); + + r.addOutput((v) => { + adders[i].a = v; + }); + stateList.push(r); + registerA.push(r); + } + + // registerB + for (let i = 0; i < BITS; i += 1) { + const eMath = svg_math.contentDocument.getElementById('operand-b-' + i); + const eAdder = svg_adder.contentDocument.getElementById('8-bit-register-b-' + i); + const r = new Register([ eMath, eAdder ]); + + r.addOutput((v) => { + adders[i].b = v; + }); + stateList.push(r); + registerB.push(r); + } + } + + doReset(); + + const txt_operand_a = document.getElementById("txt_operand-a"); + const txt_operand_b = document.getElementById("txt_operand-b"); + const radio_op_add = document.getElementById("radio_op-add"); + const radio_op_adc = document.getElementById("radio_op-adc"); + const txt_state_animations = document.getElementById("txt_state-animations"); + const txt_state_change_delay = document.getElementById("txt_state-change-delay"); + const btn_animation_defaults = document.getElementById("btn_animation-defaults"); + + document.getElementById("btn_exec").addEventListener('click', () => { + const a = myParseInt(txt_operand_a.value); + const b = myParseInt(txt_operand_b.value); + let c; + + if (isNaN(a) || isNaN(b)) { + return; + } + let i; + + function setRegisters () { + i = 0; + forEachBit(a, BITS, (v) => { + registerA[i].value = v; + i += 1; + }); + i = 0; + forEachBit(b, BITS, (v) => { + registerB[i].value = v; + i += 1; + }); + } + + if (radio_op_add.checked) { + setRegisters(); + c = (a + b) & INTMAX; + } + else if (radio_op_adc.checked) { + carry0.value = carryState.value; + setRegisters(); + c = (a + b + (carryState.value ? 1 : 0)) & INTMAX; + } + + txt_operand_a.value = c; + }); + + document.getElementById("btn_reset").addEventListener('click', () => { + doReset(); + }); + + txt_operand_a.addEventListener('change', (evt) => { + onOperandChange(evt.target.value, span_operand_a); + }); + txt_operand_b.addEventListener('change', (evt) => { + onOperandChange(evt.target.value, span_operand_b); + }); + + const animations_conf_elements = { + "animations": txt_state_animations, + "state-delay": txt_state_change_delay + }; + downloadLocalstorageData(animations_conf_elements); + + txt_state_animations.addEventListener('change', () => { + uploadLocalstorageData(animations_conf_elements); + }); + txt_state_change_delay.addEventListener('change', () => { + uploadLocalstorageData(animations_conf_elements); + }); + btn_animation_defaults.addEventListener('click', () => { + clearLocalstorageData(); + downloadLocalstorageData(animations_conf_elements); + }); +}); diff --git a/linux-casual/mp-int/the-math.svg b/linux-casual/mp-int/the-math.svg new file mode 100755 index 0000000..867adc4 --- /dev/null +++ b/linux-casual/mp-int/the-math.svg @@ -0,0 +1,556 @@ + + + + + + + + + + + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + -- cgit