Package: purescript-lua-prelude
File: src/Data/Show.lua
Function: showNumberImpl
Class: show-escaping Severity: medium
showNumberImpl delegates the core formatting to Lua's tostring, which uses the default %.14g format. PureScript's Show Number must reproduce JavaScript's Number.prototype.toString (the upstream JS FFI is var str = n.toString(); return isNaN(str + ".0") ? str : str + ".0";). Lua's %.14g diverges from JS in two independent ways. (1) Precision: it prints at most 14 significant digits, so show (1.0/3.0) yields "0.33333333333333" where PureScript/JS yields "0.3333333333333333" (16-17 sig digits, shortest round-trip). The Lua result does not even round-trip back to the same Double. (2) Number formatting: Lua switches to scientific notation far earlier than JS and zero-pads exponents to >=2 digits with an explicit sign. Concretely show (1.0e-5) gives Lua "1e-05" but JS "0.00001"; show (1.0e-7) gives Lua "1e-07" but JS "1e-7". The trailing-".0" logic itself is correct (it appends .0 only when there is no '.'/'e'/'E', matching the JS isNaN trick), but it operates on an already-wrong base string. The divergence is latent: the test suite's testNumberShow foreign import has no .lua implementation (only Utils.js exists, and it only defines throwErr), so no eval golden currently exercises Number show formatting under Lua 5.1.
Current (Lua):
showNumberImpl = (function(n)
if n ~= n then return "NaN" end
if n == math.huge then return "Infinity" end
if n == -math.huge then return "-Infinity" end
local str = tostring(n)
if str:find("[.eE]") then return str end
return str .. ".0"
end)
Expected: PureScript/JS show: 1.0/3.0 -> "0.3333333333333333"; 1.0e-5 -> "0.00001"; 1.0e-7 -> "1e-7"; 1.0e21 -> "1e+21"; 100.0 -> "100.0"; 0.1 -> "0.1". Lua tostring currently gives: 1.0/3.0 -> "0.33333333333333"; 1.0e-5 -> "1e-05"; 1.0e-7 -> "1e-07". NaN/Infinity/-Infinity and the .0 suffix logic are already correct.
Proposed fix:
Replace the bare `tostring(n)` with a base string produced by a shortest-round-trip search and then normalize the result to JS conventions, instead of relying on %.14g. Step 1 (precision): build str by trying string.format("%."..p.."g", n) for p = 1..17 and taking the first whose tonumber() equals n. Step 2 (formatting): %g alone is not sufficient because the shortest round-trip can pick scientific form for plain integers (e.g. 100 -> "1e+02") and zero-pads exponents; post-process to match JS: when the decimal exponent is in JS's plain-decimal range (roughly -6 <= exp < 21) emit fixed/decimal notation, otherwise emit `mantissa e+/-EXP` with the exponent's leading zeros stripped (keep the sign, so e-7 not e-07 and e+21 not e+021). Keep the existing NaN/Infinity/-Infinity guards and the trailing-".0" rule (append ".0" only when the final string contains no '.', 'e', or 'E'). Fully emulating Number.prototype.toString in Lua 5.1 is non-trivial; at minimum the shortest-round-trip search fixes the precision loss, and the exponent-normalization fixes the e-07/e-7 mismatch.
Found by the FFI audit; reproduced under Lua 5.1.
Package: purescript-lua-prelude
File:
src/Data/Show.luaFunction:
showNumberImplClass: show-escaping Severity: medium
showNumberImpl delegates the core formatting to Lua's tostring, which uses the default %.14g format. PureScript's Show Number must reproduce JavaScript's Number.prototype.toString (the upstream JS FFI is
var str = n.toString(); return isNaN(str + ".0") ? str : str + ".0";). Lua's %.14g diverges from JS in two independent ways. (1) Precision: it prints at most 14 significant digits, soshow (1.0/3.0)yields "0.33333333333333" where PureScript/JS yields "0.3333333333333333" (16-17 sig digits, shortest round-trip). The Lua result does not even round-trip back to the same Double. (2) Number formatting: Lua switches to scientific notation far earlier than JS and zero-pads exponents to >=2 digits with an explicit sign. Concretelyshow (1.0e-5)gives Lua "1e-05" but JS "0.00001";show (1.0e-7)gives Lua "1e-07" but JS "1e-7". The trailing-".0" logic itself is correct (it appends .0 only when there is no '.'/'e'/'E', matching the JS isNaN trick), but it operates on an already-wrong base string. The divergence is latent: the test suite'stestNumberShowforeign import has no .lua implementation (only Utils.js exists, and it only defines throwErr), so no eval golden currently exercises Number show formatting under Lua 5.1.Current (Lua):
Expected: PureScript/JS show: 1.0/3.0 -> "0.3333333333333333"; 1.0e-5 -> "0.00001"; 1.0e-7 -> "1e-7"; 1.0e21 -> "1e+21"; 100.0 -> "100.0"; 0.1 -> "0.1". Lua tostring currently gives: 1.0/3.0 -> "0.33333333333333"; 1.0e-5 -> "1e-05"; 1.0e-7 -> "1e-07". NaN/Infinity/-Infinity and the .0 suffix logic are already correct.
Proposed fix:
Replace the bare `tostring(n)` with a base string produced by a shortest-round-trip search and then normalize the result to JS conventions, instead of relying on %.14g. Step 1 (precision): build str by trying string.format("%."..p.."g", n) for p = 1..17 and taking the first whose tonumber() equals n. Step 2 (formatting): %g alone is not sufficient because the shortest round-trip can pick scientific form for plain integers (e.g. 100 -> "1e+02") and zero-pads exponents; post-process to match JS: when the decimal exponent is in JS's plain-decimal range (roughly -6 <= exp < 21) emit fixed/decimal notation, otherwise emit `mantissa e+/-EXP` with the exponent's leading zeros stripped (keep the sign, so e-7 not e-07 and e+21 not e+021). Keep the existing NaN/Infinity/-Infinity guards and the trailing-".0" rule (append ".0" only when the final string contains no '.', 'e', or 'E'). Fully emulating Number.prototype.toString in Lua 5.1 is non-trivial; at minimum the shortest-round-trip search fixes the precision loss, and the exponent-normalization fixes the e-07/e-7 mismatch.Found by the FFI audit; reproduced under Lua 5.1.