diff --git a/client/space_lua/stdlib/math.ts b/client/space_lua/stdlib/math.ts index c228d46d..7c657a6b 100644 --- a/client/space_lua/stdlib/math.ts +++ b/client/space_lua/stdlib/math.ts @@ -95,6 +95,33 @@ export const mathApi = new LuaTable({ return new LuaMultiRes([int, frac]); }), + // Returns m and e such that x = m * 2^e, 0.5 <= |m| < 1 (or m=0 when x=0). + // e is an integer. Mirrors C99/Lua. + // Special cases: frexp(0) = (0, 0); frexp(+-inf/nan) = (x, 0). + frexp: new LuaBuiltinFunction((_sf, x: number) => { + const xn = untagNumber(x); + if (xn === 0 || !isFinite(xn) || isNaN(xn)) { + return new LuaMultiRes([xn, 0]); + } + const abs = Math.abs(xn); + let e = Math.floor(Math.log2(abs)) + 1; + let m = xn / Math.pow(2, e); + if (Math.abs(m) >= 1.0) { + e += 1; + m /= 2; + } + if (Math.abs(m) < 0.5) { + e -= 1; + m *= 2; + } + return new LuaMultiRes([m, e]); + }), + + // Returns m * 2^e (the inverse of frexp). Mirrors C99/Lua. + ldexp: new LuaBuiltinFunction((_sf, m: number, e: number) => + untagNumber(m) * Math.pow(2, untagNumber(e)) + ), + // Power and logarithms exp: new LuaBuiltinFunction((_sf, x: number) => Math.exp(untagNumber(x))), log: new LuaBuiltinFunction((_sf, x: number, base?: number) => { @@ -103,6 +130,7 @@ export const mathApi = new LuaTable({ } return Math.log(untagNumber(x)) / Math.log(untagNumber(base)); }), + // Power function (deprecated in Lua 5.4 but retained for compatibility) pow: new LuaBuiltinFunction((_sf, x: number, y: number) => Math.pow(untagNumber(x), untagNumber(y)) ), @@ -121,7 +149,7 @@ export const mathApi = new LuaTable({ return Math.atan2(untagNumber(y), untagNumber(x)); }), - // Hyperbolic functions + // Hyperbolic functions (deprecated in Lua 5.4 but retained for compatibility) cosh: new LuaBuiltinFunction((_sf, x: number) => Math.cosh(untagNumber(x))), sinh: new LuaBuiltinFunction((_sf, x: number) => Math.sinh(untagNumber(x))), tanh: new LuaBuiltinFunction((_sf, x: number) => Math.tanh(untagNumber(x))), diff --git a/client/space_lua/stdlib/math_test.lua b/client/space_lua/stdlib/math_test.lua index b7db520b..503e6260 100644 --- a/client/space_lua/stdlib/math_test.lua +++ b/client/space_lua/stdlib/math_test.lua @@ -276,3 +276,34 @@ do local j2 = math.random() assertEquals(j1, j2, "determinism restored after re-seed") end + +-- frexp / ldexp +do + -- frexp: x = m * 2^e, 0.5 <= |m| < 1 + local m, e = math.frexp(8) + assertClose(m, 0.5, 1e-15, "frexp(8) m") + assertEquals(e, 4, "frexp(8) e") + + local m2, e2 = math.frexp(1) + assertClose(m2, 0.5, 1e-15, "frexp(1) m") + assertEquals(e2, 1, "frexp(1) e") + + local m3, e3 = math.frexp(-4) + assertClose(m3, -0.5, 1e-15, "frexp(-4) m") + assertEquals(e3, 3, "frexp(-4) e") + + local m4, e4 = math.frexp(0) + assertEquals(m4, 0, "frexp(0) m") + assertEquals(e4, 0, "frexp(0) e") + + -- ldexp: m * 2^e + assertClose(math.ldexp(0.5, 4), 8.0, 1e-15, "ldexp(0.5,4)") + assertClose(math.ldexp(0.5, 1), 1.0, 1e-15, "ldexp(0.5,1)") + assertClose(math.ldexp(-0.5, 3), -4.0, 1e-15, "ldexp(-0.5,3)") + assertClose(math.ldexp(0.0, 10), 0.0, 1e-15, "ldexp(0,10)") + + -- round-trip: ldexp(frexp(x)) == x + local orig = 3.14159 + local fm, fe = math.frexp(orig) + assertClose(math.ldexp(fm, fe), orig, 1e-14, "frexp/ldexp round-trip") +end