Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion prims.lua
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,8 @@ local function numToStr(n)
end
return string.format("%d", n)
end
return tostring(n)
-- non-integer: shortest round-trippable form, not LuaJIT's lossy %.14g (#24)
return R.shortest_float(n)
end

defprim("str", 1, function(x)
Expand Down
16 changes: 15 additions & 1 deletion runtime.lua
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,19 @@ M.read_all = read_all
-- mtoint: PUC 5.3+ %d-format guard (string.format("%d", x) errors there for
-- an integral float outside int64 range). nil under LuaJIT/5.1: path unchanged.
local mtoint = math.tointeger
-- Shortest round-trippable decimal for a non-integer float. LuaJIT's tostring
-- (and %g) default to 14 significant digits, which is lossy: (+ 0.1 0.2) would
-- print "0.3" instead of the actual double "0.30000000000000004". Emit the
-- smallest %.<p>g (p = 1..17) that parses back to exactly the same double, so
-- the output round-trips and matches the shen-cl/shen-rust/shen-go/ShenScript
-- reference (issue #24).
local function shortest_float(n)
for p = 1, 17 do
local s = string.format("%." .. p .. "g", n)
if tonumber(s) == n then return s end
end
return string.format("%.17g", n)
end
local function to_str(x, seen)
local t = type(x)
if t == "number" then
Expand All @@ -208,7 +221,7 @@ local function to_str(x, seen)
end
return string.format("%d", x)
end
return tostring(x)
return shortest_float(x)
elseif t == "boolean" then return x and "true" or "false"
elseif t == "string" then return '"' .. x .. '"'
elseif x == NIL then return "()"
Expand All @@ -229,5 +242,6 @@ local function to_str(x, seen)
else return tostring(x) end
end
M.to_str = to_str
M.shortest_float = shortest_float

return M
10 changes: 9 additions & 1 deletion test/primitives_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,17 @@ checkeq('(cn "foo" "bar")', '"foobar"')
checkeq('(tlstr "hello")', '"ello"')
checkeq('(pos "hello" 1)', '"e"')
checkeq("(str 42)", '"42"')
checkeq("(str 4.5)", '"4.5"') -- divergence: bare float
checkeq("(str 4.5)", '"4.5"') -- bare float (exactly representable)
checkeq("(str foo)", '"foo"')
checkeq("(str true)", '"true"')

-- issue #24: non-integer floats render in SHORTEST round-trippable form, not
-- LuaJIT's lossy %.14g. (+ 0.1 0.2) is genuinely 0.30000000000000004 as a
-- double; printing "0.3" would not round-trip. Matches shen-cl/rust/go/ShenScript.
checkeq("(str (+ 0.1 0.2))", '"0.30000000000000004"')
checkeq("(str (/ 10 4))", '"2.5"') -- exactly representable -> short form
checkeq("(str (+ 3.5 1.25))",'"4.75"')
checkeq("(str 2.0)", '"2"') -- integer-valued float still prints bare
checkeq('(string? "hi")', "true")
checkeq("(string? 1)", "false")
-- str on () is NOT representable in this port's kernel — it raises a clean,
Expand Down
Loading