fix: scientific notation with plain numbers

This commit is contained in:
2026-07-07 01:35:41 -04:00
parent c5fd2c4c0e
commit 0e5e2fa87a
+40 -9
View File
@@ -64,35 +64,66 @@ local function formatSci(value)
return out
end
local function finalizePlainString(s)
local sign, intPart, rest = s:match("^(%-?)(%d+)(.*)$")
if intPart then
s = sign .. groupDigits(intPart) .. rest
end
return displayMinus(s)
end
-- render a value in plain decimal notation, 3 significant figures.
-- IMPORTANT: C's %g spec has its own internal rule for switching to
-- exponential form ("use %e if exponent < -4 or exponent >= precision").
-- With precision 3, that means %.3g silently flips to "1e+03"-style
-- output for anything >= 1000 -- BEFORE any magnitude threshold of ours
-- gets a say. Rather than duplicating that rule with a second threshold
-- gets a say. Rather than duplicating that rule with a second threshold,
-- we let %g decide, then intercept its exponential output and re-render it through
-- formatSci() instead of ever emitting the raw "1e+03" string.
-- NOTE: this function is used for the "auto" mode ONLY. It is allowed to
-- fall back to scientific notation. For an explicit, non-negotiable
-- notation="plain" override, see formatForcedPlain below instead.
local function formatPlain(value)
local s = string.format("%.3g", value)
if s:find("[eE]") then
return formatSci(value)
end
-- split into optional sign, integer part, and remainder (decimal point
-- + fractional digits, if any) so grouping applies only to the
-- integer part -- fractional digits are never grouped
local sign, intPart, rest = s:match("^(%-?)(%d+)(.*)$")
if intPart then
s = sign .. groupDigits(intPart) .. rest
return { pandoc.Str(finalizePlainString(s)) }
end
-- render a value in plain decimal notation that NEVER falls back to
-- scientific form, regardless of magnitude, this is what
-- notation="plain" actually promises the caller. %g's own exponent-switch
-- rule (the reason formatPlain above sometimes defers to formatSci) is
-- sidestepped entirely by computing the required number of decimal places
-- directly from the value's exponent (read robustly off a %e string,
-- since %e always reports the exponent Lua/C computed rather than
-- re-deriving it ourselves and risking a boundary rounding mismatch) and
-- formatting with %f at that precision.
local function formatForcedPlain(value, sig)
sig = sig or 3
if value == 0 then return { pandoc.Str("0") } end
local sciStr = string.format("%." .. (sig - 1) .. "e", value)
local exp = tonumber(sciStr:match("e([+-]?%d+)$"))
local decimals = sig - 1 - exp
if decimals < 0 then decimals = 0 end
local s = string.format("%." .. decimals .. "f", value)
if s:find("%.") then
s = s:gsub("0+$", ""):gsub("%.$", "")
end
return { pandoc.Str(displayMinus(s)) }
return { pandoc.Str(finalizePlainString(s)) }
end
-- decide plain vs. scientific, unless overridden.
-- mode: nil/"auto" (let %g decide, see formatPlain), "sci", or "plain"
-- "plain" is a hard override -- it must never silently become scientific,
-- which is exactly the bug that motivated splitting formatForcedPlain out
-- from formatPlain rather than reusing it for both cases.
local function formatNumber(value, mode)
if value == 0 then return { pandoc.Str("0") } end
if mode == "sci" then return formatSci(value) end
return formatPlain(value) -- handles both "auto" and explicit "plain"
if mode == "plain" then return formatForcedPlain(value) end
return formatPlain(value) -- "auto"
end
local function appendAll(dst, list)