From fc9a164980d62fea4926384cce33e28e0a1d4474 Mon Sep 17 00:00:00 2001 From: maximilliangrand <214999687+maximilliangrand@users.noreply.github.com> Date: Fri, 14 Aug 2026 14:32:21 +0200 Subject: [PATCH] Fix SI-prefix rounding carry below 1e-24 When a value smaller than the smallest prefix (yocto, 1e-24) is re-rounded to the reduced precision that fits after the leading "0.", the rounding can carry into a higher power of ten (e.g. 9.99 -> 10). The digit position was taken from the original exponent, ignoring that carry, so the coefficient was placed one decimal too far right and the result was off by a factor of ten (format(".3s")(9.99e-25) returned "0.10y" instead of "1.0y"). --- src/formatPrefixAuto.js | 17 ++++++++++++++--- test/format-type-s-test.js | 8 ++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/formatPrefixAuto.js b/src/formatPrefixAuto.js index 4392459..a51ee0e 100644 --- a/src/formatPrefixAuto.js +++ b/src/formatPrefixAuto.js @@ -7,10 +7,21 @@ export default function(x, p) { if (!d) return prefixExponent = undefined, x.toPrecision(p); var coefficient = d[0], exponent = d[1], - i = exponent - (prefixExponent = Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + 1, - n = coefficient.length; + i = exponent - (prefixExponent = Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + 1; + + // Below the smallest prefix (yocto), the value is shown as “0.…”, which + // leaves fewer significant digits; re-round to that reduced precision. This + // rounding can carry into a higher power of ten (e.g. 9.99 → 10), so derive + // the digit position from the re-rounded exponent rather than the original. + if (i <= 0) { + d = formatDecimalParts(x, Math.max(0, p + i - 1)); + coefficient = d[0]; + i = d[1] - prefixExponent + 1; + } + + var n = coefficient.length; return i === n ? coefficient : i > n ? coefficient + new Array(i - n + 1).join("0") : i > 0 ? coefficient.slice(0, i) + "." + coefficient.slice(i) - : "0." + new Array(1 - i).join("0") + formatDecimalParts(x, Math.max(0, p + i - 1))[0]; // less than 1y! + : "0." + new Array(1 - i).join("0") + coefficient; // less than 1y! } diff --git a/test/format-type-s-test.js b/test/format-type-s-test.js index 96f78e6..d98de44 100644 --- a/test/format-type-s-test.js +++ b/test/format-type-s-test.js @@ -69,6 +69,14 @@ test("format(\"s\") formats numbers smaller than 1e-24 with yocto", () => { assert.strictEqual(f(-1.29e-21), "−1.2900000z"); }); +test("format(\"s\") carries into a higher power when rounding below 1e-24", () => { + assert.strictEqual(format(".3s")(9.99e-25), "1.0y"); // 0.999y → 1.0y, not 0.10y + assert.strictEqual(format(".2s")(9.9e-25), "1y"); // 0.99y → 1y, not 0.1y + assert.strictEqual(format(".3s")(9.99e-26), "0.1y"); // 0.0999y → 0.1y, not 0.01y + assert.strictEqual(format(".1s")(9.5e-25), "1y"); + assert.strictEqual(format(".5s")(9.9999e-25), "1.000y"); +}); + test("format(\"s\") formats numbers larger than 1e24 with yotta", () => { const f = format(".8s"); assert.strictEqual(f(1.23e+21), "1.2300000Z");