From d8c9f22d97e46b76398d47ffc70184159a9a4d77 Mon Sep 17 00:00:00 2001 From: atsiv sivat Date: Sat, 31 Jan 2026 15:49:47 +0000 Subject: [PATCH 01/15] add function which finds the greatest common divisor of two BigInts --- src/core/Utils.mjs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs index a9c381d7..9149d9a8 100755 --- a/src/core/Utils.mjs +++ b/src/core/Utils.mjs @@ -1263,6 +1263,18 @@ class Utils { return Utils.gcd(y, x % y); } + /** + * Finds the greatest common divisor of two BigInt numbers. + * + * @author atsiv1 [atsiv1@proton.me] + * @param {BigInt} x + * @param {BigInt} y + * @returns {BigInt} + */ + static gcdBigInt(a, b) { + return b === 0n ? a : Utils.gcdBigInt(b, a % b); + } + /** * Finds the modular inverse of two values. From 9706fbb10be1bdd67662c270594754eaebadc8da Mon Sep 17 00:00:00 2001 From: atsiv sivat Date: Sat, 31 Jan 2026 15:53:51 +0000 Subject: [PATCH 02/15] Add IEEE-754 Binary64 conversion library --- src/core/lib/IEEEBinary.mjs | 353 ++++++++++++++++++++++++++++++++++++ 1 file changed, 353 insertions(+) create mode 100644 src/core/lib/IEEEBinary.mjs diff --git a/src/core/lib/IEEEBinary.mjs b/src/core/lib/IEEEBinary.mjs new file mode 100644 index 00000000..27360b52 --- /dev/null +++ b/src/core/lib/IEEEBinary.mjs @@ -0,0 +1,353 @@ +/** + * IEEEBinary functions. + * + * Convert between: + * - exact decimal strings ↔ IEEE 754 double-precision (binary64) bits + * using BigInt rational arithmetic and correct Round-to-Nearest-Even (RNE). + * + * @author atsiv1 [atsiv1@proton.me] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Utils from "../Utils.mjs"; + +// IEEE 754 double-precision constants +const EXP_BITS = 11n; +const MANT_BITS = 52n; +const PRECISION = MANT_BITS + 1n; +const BIAS = (1n << (EXP_BITS - 1n)) - 1n; +const MAX_EXP = (1n << EXP_BITS) - 1n; + +const EXP_BITS_NUM = Number(EXP_BITS); +const MANT_BITS_NUM = Number(MANT_BITS); + + +/** + * Compute 10^exp as BigInt without using ** on BigInt, to avoid + * environments that try to coerce BigInt to Number. + * + * @param {bigint} exp Non-negative BigInt exponent. + * @returns {bigint} + */ +function pow10BigInt(exp) { + if (exp < 0n) { + throw new RangeError("pow10BigInt expects non-negative exponent"); + } + let e = exp; + let result = 1n; + let base = 10n; + + // fast exponentiation by squaring + while (e > 0n) { + if ((e & 1n) === 1n) { + result *= base; + } + base *= base; + e >>= 1n; + } + return result; +} + +/** + * Converts an exact rational number (num / den) into its decimal string representation. + * + * The result is returned as a string. If the decimal is repeating, the repeating + * part will be enclosed in parentheses (e.g., "0.(3)"). + * + * @param {bigint} num - The numerator of the fraction. + * @param {bigint} den - The denominator of the fraction. + * @returns {string} The decimal representation of the fraction, including repeating decimals in parentheses. + * + * @example + * // returns "0.5" + * fractionToDecimal(1n, 2n); + * + * // returns "0.(3)" + * fractionToDecimal(1n, 3n); + * + * // returns "1.25" + * fractionToDecimal(5n, 4n); + * + * @note For many binary64 values, the repeating cycle can be long, which is + * mathematically correct but may produce large strings. + */ +function fractionToDecimal(num, den) { + const intPart = num / den; + let rem = num % den; + + if (rem === 0n) { + return intPart.toString(); + } + + const seen = new Map(); + const digits = []; + let pos = 0; + + while (rem !== 0n) { + if (seen.has(rem)) { + const p = seen.get(rem); + digits.splice(p, 0, "("); + digits.push(")"); + break; + } + seen.set(rem, pos++); + + rem *= 10n; + digits.push((rem / den).toString()); + rem %= den; + } + + return intPart.toString() + "." + digits.join(""); +} + +/** + * Converts a 64-bit IEEE-754 binary64 representation into its exact + * decimal string. + * + * Note: While binary64 can only represent a subset of all real numbers, + * every representable value has a finite decimal expansion. This function + * uses BigInt arithmetic to retrieve the exact value without the + * precision loss typically encountered in standard floating-point arithmetic. + * + * @param {string} binary64String + * @returns {string} + * + * @example + * // returns "10" + * FromIEEE754Float64("0 10000000010 0100000000000000000000000000000000000000000000000000"); + * + * // returns "7.29999999999999982236431605997495353221893310546875" + * FromIEEE754Float64("0 10000000001 1101001100110011001100110011001100110011001100110011); + */ +export function FromIEEE754Float64(binary64String) { + const bin = binary64String.trim().replace(/\s+/g, ""); + + if (bin.length !== 64) { + throw new Error("Input must be 64 bits."); + } + + const signBit = bin[0]; + const expBits = bin.slice(1, 12); + const mantBits = bin.slice(12); + + const sign = signBit === "1" ? "-" : ""; + const exp = parseInt(expBits, 2); + const mant = BigInt("0b" + mantBits); + + // Special values + if (exp === Number(MAX_EXP)) { + if (mant === 0n) { + return sign + "Infinity"; + } + return "NaN"; + } + + let e; + let m; + + if (exp === 0) { + // Subnormal: exponent is 1 - BIAS, no implicit leading 1 + e = 1n - BIAS - MANT_BITS; + m = mant; + } else { + // Normal: exponent is exp - BIAS, with implicit leading 1 + e = BigInt(exp) - BIAS - MANT_BITS; + m = mant | (1n << MANT_BITS); + } + + // If exponent is non-negative, result is an integer + if (e >= 0n) { + return sign + (m << e).toString(); + } + + // Otherwise, it's a fraction m / 2^(-e) + const den = 1n << (-e); + return sign + fractionToDecimal(m, den); +} + + +/** + * + * Converts a decimal number into its IEEE-754 double-precision + * (binary64) bit representation. + * + * @param {string|number|BigInt} input + * @returns {string} + * + * @example + * // returns "0 10000000000 0000000000000000000000000000000000000000000000000000" + * ToIEEE754Float64("2"); + * + * // returns "0 10000000011 0111010011001100110011001100110011001100110011001101" + * ToIEEE754Float64("23.300000000000000710542735760100185871124267578125"); + */ +export function ToIEEE754Float64(input) { + input = String(input).trim(); + + const expOnes = "1".repeat(EXP_BITS_NUM); + const mantZeros = "0".repeat(MANT_BITS_NUM); + + // Special values: NaN / ±Infinity + if (/^(NaN)$/i.test(input)) { + return `0 ${expOnes} 1${"0".repeat(MANT_BITS_NUM - 1)}`; + } + + if (/^[+-]?inf(inity)?$/i.test(input)) { + const s = input.startsWith("-") ? "1" : "0"; + return `${s} ${expOnes} ${mantZeros}`; + } + + let sign = 0n; + if (input.startsWith("-")) { + sign = 1n; + input = input.slice(1); + } else if (input.startsWith("+")) { + input = input.slice(1); + } + + let sci = 0n; + const sciMatch = input.match(/^(.*)e([+-]?\d+)$/i); + if (sciMatch) { + input = sciMatch[1]; + sci = BigInt(sciMatch[2]); + } + + let [intStr, fracStr] = input.split("."); + intStr = (intStr || "0").replace(/_/g, ""); + fracStr = (fracStr || "").replace(/_/g, ""); + + let N = BigInt(intStr); + let D = 1n; + + if (fracStr.length > 0) { + const pow10 = pow10BigInt(BigInt(fracStr.length)); + N = N * pow10 + BigInt(fracStr); + D = pow10; + } + + // Apply scientific exponent + if (sci > 0n) { + N *= pow10BigInt(sci); + } else if (sci < 0n) { + D *= pow10BigInt(-sci); + } + + // Zero (preserve sign, including -0) + if (N === 0n) { + const expZero = "0".repeat(EXP_BITS_NUM); + return `${sign} ${expZero} ${mantZeros}`; + } + + // Reduce the fraction + const g = Utils.gcdBigInt(N, D); + N /= g; + D /= g; + + // Compute binary exponent approximation (with fix) + const eN = BigInt(N.toString(2).length - 1); + const eD = BigInt(D.toString(2).length - 1); + let e2 = eN - eD; + + const GRS = 3n; // Guard, Round, Sticky bits + const totalBits = PRECISION + GRS; + + // Approximate bit-length of normalized N/D: e2 + 1 + const currentBitLength = eN - eD + 1n; + + // Shift so we get 'totalBits' bits of precision in the quotient + let shift = totalBits - currentBitLength; + + let num, den; + + if (shift >= 0n) { + num = N << shift; + den = D; + } else { + num = N; + den = D << (-shift); + } + + let full = num / den; + let rem = num % den; + + // Normalisation Fix + if (full < (1n << (totalBits - 1n))) { + e2 -= 1n; + shift += 1n; + + if (shift >= 0n) { + num <<= 1n; + } else { + den >>= 1n; + } + + full = num / den; + rem = num % den; + } + + const roundMask = (1n << GRS) - 1n; + const roundBits = full & roundMask; + let mant = full >> GRS; + + // Extract G, R, S bits + const G = (roundBits >> 2n) & 1n; + const R = (roundBits >> 1n) & 1n; + const S = ((roundBits & 1n) === 1n || rem !== 0n) ? 1n : 0n; + + let roundUp = false; + + if (G === 1n) { + if (R === 1n || S === 1n) { + // strictly > 0.5 + roundUp = true; + } else if ((mant & 1n) === 1n) { + // exactly 0.5 → round to even + roundUp = true; + } + } + + if (roundUp) mant++; + + // Renormalize if mantissa overflowed + if (mant >= (1n << PRECISION)) { + mant >>= 1n; + e2 += 1n; + } + + // Overflow → Infinity + if (e2 + BIAS >= MAX_EXP) { + return `${sign} ${expOnes} ${mantZeros}`; + } + + // Normal number (exponent >= 1) + if (e2 + BIAS >= 1n) { + const expValue = e2 + BIAS; + const expBits = expValue.toString(2).padStart(EXP_BITS_NUM, "0"); + + // Remove the implicit leading 1: mantissa stores only the 52 explicit bits + const mantBits = (mant & ((1n << MANT_BITS) - 1n)) + .toString(2) + .padStart(MANT_BITS_NUM, "0"); + + return `${sign} ${expBits} ${mantBits}`; + } + + // Subnormal numbers (exponent field = 0) + const minNormalExponentValue = 1n - BIAS; + const subShift = minNormalExponentValue - e2; + + let subMant = mant >> subShift; + + // Subnormal mantissa uses all 52 explicit bits + subMant &= (1n << MANT_BITS) - 1n; + + const expZero = "0".repeat(EXP_BITS_NUM); + + if (subMant === 0n) { + return `${sign} ${expZero} ${mantZeros}`; + } + + const subMantBits = subMant.toString(2).padStart(MANT_BITS_NUM, "0"); + return `${sign} ${expZero} ${subMantBits}`; +} From 02cc9a1ccf92cfe990c19cd6c7576f5925f00784 Mon Sep 17 00:00:00 2001 From: atsiv sivat Date: Sat, 31 Jan 2026 15:54:16 +0000 Subject: [PATCH 03/15] Add FromIEEEBinary Operation --- src/core/operations/FromIEEEBinary.mjs | 47 ++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/core/operations/FromIEEEBinary.mjs diff --git a/src/core/operations/FromIEEEBinary.mjs b/src/core/operations/FromIEEEBinary.mjs new file mode 100644 index 00000000..9bbe2bc4 --- /dev/null +++ b/src/core/operations/FromIEEEBinary.mjs @@ -0,0 +1,47 @@ +/** + * @author atsiv1 + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { FromIEEE754Float64 } from "../lib/IEEEBinary.mjs"; + +/** + * From IEEEBinary operation + */ +class FromIEEEBinary extends Operation { + /** + * FromIEEEBinary constructor + */ + constructor() { + super(); + + this.name = "From IEEEBinary"; + this.module = "Default"; + this.description = ` + Converts a 64-bit IEEE-754 binary64 representation (float64) + into its exact decimal value.

+ Example: 0 10000000010 0100000000000000000000000000000000000000000000000000 + becomes 10.`; + this.infoURL = "https://en.wikipedia.org/wiki/IEEE_754"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input || input.trim().length === 0) { + return ""; + } + + return FromIEEE754Float64(input); + } +} + +export default FromIEEEBinary; From 443eabf160bf448934cde7670976e1592c0ed731 Mon Sep 17 00:00:00 2001 From: atsiv sivat Date: Sat, 31 Jan 2026 15:54:27 +0000 Subject: [PATCH 04/15] Add ToIEEEBinary Operation --- src/core/operations/ToIEEEBinary.mjs | 46 ++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/core/operations/ToIEEEBinary.mjs diff --git a/src/core/operations/ToIEEEBinary.mjs b/src/core/operations/ToIEEEBinary.mjs new file mode 100644 index 00000000..db93e920 --- /dev/null +++ b/src/core/operations/ToIEEEBinary.mjs @@ -0,0 +1,46 @@ +/** + * @author atsiv1 + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { ToIEEE754Float64 } from "../lib/IEEEBinary.mjs"; + +/** + * To IEEEBinary operation + */ +class ToIEEEBinary extends Operation { + /** + * ToIEEEBinary constructor + */ + constructor() { + super(); + + this.name = "To IEEEBinary"; + this.module = "Default"; + this.description = ` + Converts a decimal number into IEEE-754 double-precision float64.

+ Example: 2 becomes + 0 10000000000 0000000000000000000000000000000000000000000000000000.`; + this.infoURL = "https://en.wikipedia.org/wiki/IEEE_754"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input || input.trim().length === 0) { + return ""; + } + + return ToIEEE754Float64(input); + } +} + +export default ToIEEEBinary; From 7cfdc65831a9c7489d1de25ce3ee18a61849d218 Mon Sep 17 00:00:00 2001 From: atsiv sivat Date: Sat, 31 Jan 2026 15:54:59 +0000 Subject: [PATCH 05/15] Add To and From IEEEBinary Operations in Categories file --- src/core/config/Categories.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 434c8bb6..603936b0 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -20,6 +20,8 @@ "From Binary", "To Octal", "From Octal", + "To IEEEBinary", + "From IEEEBinary", "To Base32", "From Base32", "To Base45", From f276f40981a48c0810f3c16e403fe963c1d60291 Mon Sep 17 00:00:00 2001 From: atsiv sivat Date: Sat, 31 Jan 2026 15:55:22 +0000 Subject: [PATCH 06/15] add tests for To and From IEEEBinary Operations --- tests/operations/tests/IEEEBinary.mjs | 185 ++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 tests/operations/tests/IEEEBinary.mjs diff --git a/tests/operations/tests/IEEEBinary.mjs b/tests/operations/tests/IEEEBinary.mjs new file mode 100644 index 00000000..c7f4569c --- /dev/null +++ b/tests/operations/tests/IEEEBinary.mjs @@ -0,0 +1,185 @@ +/** + * IEEE754 Float64 tests. + * + * @author atsiv1 [atsiv1@proton.me] + + * + * @copyright Crown Copyright + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "From IEEEBinary: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "From IEEEBinary", + args: [], + }, + ], + }, + { + name: "From IEEEBinary: Zero (positive)", + input: "0000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "0", + recipeConfig: [ + { + op: "From IEEEBinary", + args: [], + }, + ], + }, + { + name: "From IEEEBinary: Zero (negative)", + input: "1000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "-0", + recipeConfig: [ + { + op: "From IEEEBinary", + args: [], + }, + ], + }, + { + name: "From IEEEBinary: 4.5", + input: "0100000000010010000000000000000000000000000000000000000000000000", + expectedOutput: "4.5", + recipeConfig: [ + { + op: "From IEEEBinary", + args: [], + }, + ], + }, + { + name: "From IEEEBinary: 0.1", + input: "0011111110111001100110011001100110011001100110011001100110011010", + expectedOutput: + "0.1000000000000000055511151231257827021181583404541015625", + recipeConfig: [ + { + op: "From IEEEBinary", + args: [], + }, + ], + }, + { + name: "From IEEEBinary: Infinity", + input: "0111111111110000000000000000000000000000000000000000000000000000", + expectedOutput: "Infinity", + recipeConfig: [ + { + op: "From IEEEBinary", + args: [], + }, + ], + }, + { + name: "From IEEEBinary: -Infinity", + input: "1111111111110000000000000000000000000000000000000000000000000000", + expectedOutput: "-Infinity", + recipeConfig: [ + { + op: "From IEEEBinary", + args: [], + }, + ], + }, + { + name: "From IEEEBinary: NaN", + input: "0111111111111000000000000000000000000000000000000000000000000000", + expectedOutput: "NaN", + recipeConfig: [ + { + op: "From IEEEBinary", + args: [], + }, + ], + }, + { + name: "To IEEEBinary: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "To IEEEBinary", + args: [], + }, + ], + }, + { + name: "To IEEEBinary: Zero", + input: "0", + expectedOutput: + "0 00000000000 0000000000000000000000000000000000000000000000000000", + recipeConfig: [ + { + op: "To IEEEBinary", + args: [], + }, + ], + }, + { + name: "To IEEEBinary: -0", + input: "-0", + expectedOutput: + "1 00000000000 0000000000000000000000000000000000000000000000000000", + recipeConfig: [ + { + op: "To IEEEBinary", + args: [], + }, + ], + }, + { + name: "To IEEEBinary: 4.5", + input: "4.5", + expectedOutput: + "0 10000000001 0010000000000000000000000000000000000000000000000000", + recipeConfig: [ + { + op: "To IEEEBinary", + args: [], + }, + ], + }, + { + name: "To IEEEBinary: 0.1", + input: "0.1", + expectedOutput: + "0 01111111011 1001100110011001100110011001100110011001100110011010", + recipeConfig: [ + { + op: "To IEEEBinary", + args: [], + }, + ], + }, + { + name: "To IEEEBinary: Infinity", + input: "Infinity", + expectedOutput: + "0 11111111111 0000000000000000000000000000000000000000000000000000", + recipeConfig: [ + { + op: "To IEEEBinary", + args: [], + }, + ], + }, + { + name: "To IEEEBinary: NaN", + input: "NaN", + expectedOutput: + "0 11111111111 1000000000000000000000000000000000000000000000000000", + recipeConfig: [ + { + op: "To IEEEBinary", + args: [], + }, + ], + }, +]); From de789bfb33a230c38396cd21d746e8a96302d9b1 Mon Sep 17 00:00:00 2001 From: atsiv sivat Date: Sat, 31 Jan 2026 15:55:54 +0000 Subject: [PATCH 07/15] Add IEEEBinary test file to index file for tests --- tests/operations/index.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index f147e9e7..06dfac5b 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -85,6 +85,7 @@ import "./tests/HaversineDistance.mjs"; import "./tests/Hex.mjs"; import "./tests/Hexdump.mjs"; import "./tests/HKDF.mjs"; +import "./tests/IEEEBinary.mjs"; import "./tests/Image.mjs"; import "./tests/IndexOfCoincidence.mjs"; import "./tests/JA3Fingerprint.mjs"; From 8a75be1b2053208eeb11e982034f6540408b5609 Mon Sep 17 00:00:00 2001 From: atsiv sivat Date: Sun, 8 Mar 2026 00:55:06 +0000 Subject: [PATCH 08/15] add function normaliseInput to help sanatise and validate input before parsing --- src/core/lib/IEEEBinary.mjs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/lib/IEEEBinary.mjs b/src/core/lib/IEEEBinary.mjs index 27360b52..16e838ee 100644 --- a/src/core/lib/IEEEBinary.mjs +++ b/src/core/lib/IEEEBinary.mjs @@ -22,6 +22,12 @@ const MAX_EXP = (1n << EXP_BITS) - 1n; const EXP_BITS_NUM = Number(EXP_BITS); const MANT_BITS_NUM = Number(MANT_BITS); +function normaliseInput(input) { + if (input === null || input === undefined) + throw new Error("Invalid decimal number"); + return String(input).trim(); +} + /** * Compute 10^exp as BigInt without using ** on BigInt, to avoid @@ -183,7 +189,7 @@ export function FromIEEE754Float64(binary64String) { * ToIEEE754Float64("23.300000000000000710542735760100185871124267578125"); */ export function ToIEEE754Float64(input) { - input = String(input).trim(); + input = normaliseInput(input); const expOnes = "1".repeat(EXP_BITS_NUM); const mantZeros = "0".repeat(MANT_BITS_NUM); From e95782eba7440411ec1649f47588002f5988fc58 Mon Sep 17 00:00:00 2001 From: atsiv sivat Date: Sun, 8 Mar 2026 00:57:03 +0000 Subject: [PATCH 09/15] add function safeBigInt to wrap BigInt values and provide useful errors --- src/core/lib/IEEEBinary.mjs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/core/lib/IEEEBinary.mjs b/src/core/lib/IEEEBinary.mjs index 16e838ee..fc407245 100644 --- a/src/core/lib/IEEEBinary.mjs +++ b/src/core/lib/IEEEBinary.mjs @@ -28,6 +28,14 @@ function normaliseInput(input) { return String(input).trim(); } +function safeBigInt(str, context="number") { + try { + return BigInt(str); + } catch { + throw new Error(`Invalid ${context}`); + } +} + /** * Compute 10^exp as BigInt without using ** on BigInt, to avoid @@ -216,19 +224,19 @@ export function ToIEEE754Float64(input) { const sciMatch = input.match(/^(.*)e([+-]?\d+)$/i); if (sciMatch) { input = sciMatch[1]; - sci = BigInt(sciMatch[2]); + sci = safeBigInt(sciMatch[2], "exponent"); } let [intStr, fracStr] = input.split("."); intStr = (intStr || "0").replace(/_/g, ""); fracStr = (fracStr || "").replace(/_/g, ""); - let N = BigInt(intStr); + let N = safeBigInt(intStr, "integer part"); let D = 1n; if (fracStr.length > 0) { - const pow10 = pow10BigInt(BigInt(fracStr.length)); - N = N * pow10 + BigInt(fracStr); + const pow10 = pow10BigInt(BigInt(fracStr.length)); + N = N * pow10 + safeBigInt(fracStr, "fractional part"); D = pow10; } From fd1be923b2fdedf4f5ed5ded418591c32181ed11 Mon Sep 17 00:00:00 2001 From: atsiv sivat Date: Sun, 8 Mar 2026 00:59:21 +0000 Subject: [PATCH 10/15] add function to enforce base 10 decimal sytnax before parsing --- src/core/lib/IEEEBinary.mjs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/core/lib/IEEEBinary.mjs b/src/core/lib/IEEEBinary.mjs index fc407245..cf805c7f 100644 --- a/src/core/lib/IEEEBinary.mjs +++ b/src/core/lib/IEEEBinary.mjs @@ -36,6 +36,12 @@ function safeBigInt(str, context="number") { } } +function validateDecimal(input) { + // validates optional-sign base-10 decimal input: integers, decimals with optional digits before or after the decimal point, and scientific notation + if (!/^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i.test(input)) + throw new Error("Invalid decimal number"); +} + /** * Compute 10^exp as BigInt without using ** on BigInt, to avoid @@ -212,6 +218,8 @@ export function ToIEEE754Float64(input) { return `${s} ${expOnes} ${mantZeros}`; } + validateDecimal(input); + let sign = 0n; if (input.startsWith("-")) { sign = 1n; From 8fd7aec61c35324587b7824446a74cfa2bd3b531 Mon Sep 17 00:00:00 2001 From: atsiv sivat Date: Sun, 8 Mar 2026 01:00:29 +0000 Subject: [PATCH 11/15] add function to validate 64 bit binary input --- src/core/lib/IEEEBinary.mjs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/core/lib/IEEEBinary.mjs b/src/core/lib/IEEEBinary.mjs index cf805c7f..a3a9a3b1 100644 --- a/src/core/lib/IEEEBinary.mjs +++ b/src/core/lib/IEEEBinary.mjs @@ -42,6 +42,18 @@ function validateDecimal(input) { throw new Error("Invalid decimal number"); } +function validateBinary64(input) { + input = normaliseInput(input); + + if (!/^[01\s]+$/.test(input)) + throw new Error("Binary64 must contain only 0 and 1"); + + if (input.replace(/\s+/g,"").length !== 64) + throw new Error("Binary64 must be exactly 64 bits"); + + return input.replace(/\s+/g,""); +} + /** * Compute 10^exp as BigInt without using ** on BigInt, to avoid @@ -141,7 +153,7 @@ function fractionToDecimal(num, den) { * FromIEEE754Float64("0 10000000001 1101001100110011001100110011001100110011001100110011); */ export function FromIEEE754Float64(binary64String) { - const bin = binary64String.trim().replace(/\s+/g, ""); + const bin = validateBinary64(binary64String); if (bin.length !== 64) { throw new Error("Input must be 64 bits."); From e18a9b04d40ac409222a6ee1937ed31746ee31dc Mon Sep 17 00:00:00 2001 From: atsiv sivat Date: Sun, 8 Mar 2026 01:01:17 +0000 Subject: [PATCH 12/15] add space and change spelling of words normalized to noramlised --- src/core/lib/IEEEBinary.mjs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/lib/IEEEBinary.mjs b/src/core/lib/IEEEBinary.mjs index a3a9a3b1..d9067cbd 100644 --- a/src/core/lib/IEEEBinary.mjs +++ b/src/core/lib/IEEEBinary.mjs @@ -240,6 +240,7 @@ export function ToIEEE754Float64(input) { input = input.slice(1); } + let sci = 0n; const sciMatch = input.match(/^(.*)e([+-]?\d+)$/i); if (sciMatch) { @@ -286,7 +287,7 @@ export function ToIEEE754Float64(input) { const GRS = 3n; // Guard, Round, Sticky bits const totalBits = PRECISION + GRS; - // Approximate bit-length of normalized N/D: e2 + 1 + // Approximate bit-length of normalised N/D: e2 + 1 const currentBitLength = eN - eD + 1n; // Shift so we get 'totalBits' bits of precision in the quotient @@ -343,7 +344,7 @@ export function ToIEEE754Float64(input) { if (roundUp) mant++; - // Renormalize if mantissa overflowed + // Renormalise if mantissa overflowed if (mant >= (1n << PRECISION)) { mant >>= 1n; e2 += 1n; From 5e427e8531eb23d38c496f879325a0b10c6376a7 Mon Sep 17 00:00:00 2001 From: atsiv sivat Date: Sun, 8 Mar 2026 01:04:26 +0000 Subject: [PATCH 13/15] use OperationError to handle errors and also normalise input handling --- src/core/operations/FromIEEEBinary.mjs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/core/operations/FromIEEEBinary.mjs b/src/core/operations/FromIEEEBinary.mjs index 9bbe2bc4..7417108f 100644 --- a/src/core/operations/FromIEEEBinary.mjs +++ b/src/core/operations/FromIEEEBinary.mjs @@ -4,6 +4,7 @@ * @license Apache-2.0 */ +import OperationError from "../errors/OperationError.mjs"; import Operation from "../Operation.mjs"; import { FromIEEE754Float64 } from "../lib/IEEEBinary.mjs"; @@ -35,13 +36,22 @@ class FromIEEEBinary extends Operation { * @param {Object[]} args * @returns {string} */ - run(input, args) { - if (!input || input.trim().length === 0) { - return ""; - } + run(input, args) { + if (input === null || input === undefined) + return ""; + + input = String(input); + + if (input.trim().length === 0) + return ""; + + try { return FromIEEE754Float64(input); + } catch (err) { + throw new OperationError(err.message); } +} } export default FromIEEEBinary; From b087317acd960debd2a46024f65ac90387b556b1 Mon Sep 17 00:00:00 2001 From: atsiv sivat Date: Sun, 8 Mar 2026 01:05:06 +0000 Subject: [PATCH 14/15] use OperationError to handle errors and also normalise input handling in operation To IEEEBinary --- src/core/operations/ToIEEEBinary.mjs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/core/operations/ToIEEEBinary.mjs b/src/core/operations/ToIEEEBinary.mjs index db93e920..3faa7fb8 100644 --- a/src/core/operations/ToIEEEBinary.mjs +++ b/src/core/operations/ToIEEEBinary.mjs @@ -4,6 +4,7 @@ * @license Apache-2.0 */ +import OperationError from "../errors/OperationError.mjs"; import Operation from "../Operation.mjs"; import { ToIEEE754Float64 } from "../lib/IEEEBinary.mjs"; @@ -34,13 +35,23 @@ class ToIEEEBinary extends Operation { * @param {Object[]} args * @returns {string} */ - run(input, args) { - if (!input || input.trim().length === 0) { - return ""; - } + run(input, args) { + if (input === null || input === undefined) + return ""; + input = String(input); + + if (input.trim().length === 0) + return ""; + + try { return ToIEEE754Float64(input); + } catch (err) { + throw new OperationError(err.message); + } } +} + export default ToIEEEBinary; From b82243c42aedb6a21c59b024831cae4f6c203dcc Mon Sep 17 00:00:00 2001 From: atsiv sivat Date: Sun, 8 Mar 2026 17:20:50 +0000 Subject: [PATCH 15/15] move comment placement to the top of the line --- src/core/lib/IEEEBinary.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/lib/IEEEBinary.mjs b/src/core/lib/IEEEBinary.mjs index d9067cbd..4f5096ce 100644 --- a/src/core/lib/IEEEBinary.mjs +++ b/src/core/lib/IEEEBinary.mjs @@ -284,7 +284,8 @@ export function ToIEEE754Float64(input) { const eD = BigInt(D.toString(2).length - 1); let e2 = eN - eD; - const GRS = 3n; // Guard, Round, Sticky bits + // Guard, Round, Sticky bits + const GRS = 3n; const totalBits = PRECISION + GRS; // Approximate bit-length of normalised N/D: e2 + 1