Merge pull request #1852 from mjf/space-lua-add-frexp-and-ldexp

[Space Lua] Add `math.frexp` and `math.ldexp` and tests
This commit is contained in:
Zef Hemel
2026-02-25 10:38:06 +01:00
committed by GitHub
2 changed files with 60 additions and 1 deletions
+29 -1
View File
@@ -118,6 +118,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) => {
@@ -126,6 +153,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))
),
@@ -144,7 +172,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))),
+31
View File
@@ -317,3 +317,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