Files
plainleaf/client/space_lua/stdlib/math_test.lua
T
Zef HemelandGitHub 924f7cacc3 Merge pull request #1852 from mjf/space-lua-add-frexp-and-ldexp
[Space Lua] Add `math.frexp` and `math.ldexp` and tests
2026-02-25 10:38:06 +01:00

351 lines
11 KiB
Lua

local function assertEquals(a, b, msg)
if a ~= b then
error((msg or "assertEquals failed") .. ": expected " .. tostring(b) .. " got " .. tostring(a))
end
end
local function assertTrue(v, msg)
if not v then
error(msg or "assertTrue failed")
end
end
local function assertClose(a, b, eps, msg)
eps = eps or 1e-12
if a ~= a and b ~= b then
return
end
if a == b then
return
end
local d = a - b
if d < 0 then d = -d end
if d > eps then
error((msg or "assertClose failed") .. ": expected " .. tostring(b) .. " got " .. tostring(a))
end
end
local function assertError(fn, msg)
local ok, err = pcall(fn)
if ok then
error((msg or "assertError failed") .. ": expected an error but none was raised")
end
end
-- math.type (basic)
do
assertEquals(math.type(0), "integer", "math.type(0)")
assertEquals(math.type(1), "integer", "math.type(1)")
assertEquals(math.type(0.0), "float", "math.type(0.0)")
assertEquals(math.type(-0.0), "float", "math.type(-0.0)")
assertEquals(math.type(1.5), "float", "math.type(1.5)")
end
-- constants
do
assertTrue(type(math.pi) == "number", "math.pi exists")
assertTrue(type(math.huge) == "number", "math.huge exists")
end
-- abs
do
assertEquals(math.abs(-5), 5, "abs(-5)")
assertEquals(math.abs(-3), 3, "abs(-3)")
assertEquals(math.abs(3), 3, "abs(3)")
assertClose(math.abs(-3.25), 3.25, 1e-12, "abs(-3.25)")
end
-- floor/ceil (rounding correctness)
do
assertEquals(math.floor(3.7), 3, "floor(3.7)")
assertEquals(math.floor(3), 3, "floor(3)")
assertEquals(math.floor(3.0), 3, "floor(3.0)")
assertEquals(math.floor(3.1), 3, "floor(3.1)")
assertEquals(math.floor(3.9), 3, "floor(3.9)")
assertEquals(math.floor(-3.1), -4, "floor(-3.1)")
assertEquals(math.floor(-3.9), -4, "floor(-3.9)")
assertEquals(math.ceil(3.3), 4, "ceil(3.3)")
assertEquals(math.ceil(3), 3, "ceil(3)")
assertEquals(math.ceil(3.0), 3, "ceil(3.0)")
assertEquals(math.ceil(3.1), 4, "ceil(3.1)")
assertEquals(math.ceil(3.9), 4, "ceil(3.9)")
assertEquals(math.ceil(-3.1), -3, "ceil(-3.1)")
assertEquals(math.ceil(-3.9), -3, "ceil(-3.9)")
end
-- min/max
do
assertEquals(math.max(1, 2, 3, 4), 4, "max(1,2,3,4)")
assertEquals(math.min(1, 2, 3, 4), 1, "min(1,2,3,4)")
assertEquals(math.max(1, 2, 3), 3, "max")
assertEquals(math.min(1, 2, 3), 1, "min")
assertEquals(math.max(-1, -2), -1, "max negative")
assertEquals(math.min(-1, -2), -2, "min negative")
end
-- modf
do
local i, f = math.modf(3.5)
assertEquals(math.type(i), "integer", "modf int part is integer")
assertEquals(math.type(f), "float", "modf frac part is float")
assertEquals(i, 3, "modf(3.5) int")
assertClose(f, 0.5, 1e-12, "modf(3.5) frac")
local i2, f2 = math.modf(-3.5)
assertEquals(math.type(i2), "integer", "modf neg int part is integer")
assertEquals(math.type(f2), "float", "modf neg frac part is float")
assertEquals(i2, -3, "modf(-3.5) int")
assertClose(f2, -0.5, 1e-12, "modf(-3.5) frac")
local i3, f3 = math.modf(4.0)
assertEquals(math.type(i3), "integer", "modf whole int part is integer")
assertEquals(math.type(f3), "float", "modf whole frac is float")
assertEquals(i3, 4, "modf(4.0) int")
assertEquals(f3, 0.0, "modf(4.0) frac")
end
-- sqrt/exp/log/pow
do
assertEquals(math.exp(0), 1, "exp(0)")
assertClose(math.log(math.exp(1)), 1, 1e-12, "log(exp(1))")
assertClose(math.log(8, 2), 3, 1e-12, "log base 2 of 8")
assertEquals(math.pow(2, 3), 8, "pow(2,3)")
assertEquals(math.sqrt(9), 3, "sqrt(9)")
end
-- trigonomic
do
assertEquals(math.cos(0), 1, "cos(0)")
assertEquals(math.sin(0), 0, "sin(0)")
assertEquals(math.tan(0), 0, "tan(0)")
assertEquals(math.acos(1), 0, "acos(1)")
assertEquals(math.asin(0), 0, "asin(0)")
assertEquals(math.atan(0), 0, "atan(0)")
assertEquals(math.atan(0, 1), 0, "atan(0,1)")
end
-- hyperbolic
do
assertEquals(math.cosh(0), 1, "cosh(0)")
assertEquals(math.sinh(0), 0, "sinh(0)")
assertEquals(math.tanh(0), 0, "tanh(0)")
end
-- remainder
do
assertEquals(math.fmod(7, 3), 1, "fmod(7,3)")
end
-- random
do
local a1 = math.random()
assertTrue(a1 >= 0 and a1 < 1, "random() range")
local aN = math.random(10)
assertTrue(aN >= 1 and aN <= 10, "random(10) range")
local aR = math.random(5, 10)
assertTrue(aR >= 5 and aR <= 10, "random(5,10) range")
end
-- unsigned less-than
do
assertEquals(math.ult(1, 2), true, "ult(1,2)")
assertEquals(math.ult(2, 1), false, "ult(2,1)")
end
-- reported regression
do
local x1 = 8 * 0.5
local x2 = 8 * 0.49
local x3 = 9 * 0.5
assertClose(x1, 4.0, 0, "8*0.5 == 4.0")
assertClose(x2, 3.92, 1e-12, "8*0.49 == 3.92")
assertClose(x3, 4.5, 1e-12, "9*0.5 == 4.5")
local f1 = math.floor(x1)
local f2 = math.floor(x2)
local f3 = math.floor(x3)
assertTrue(f1 == f1, "floor(8*0.5) not NaN")
assertTrue(f2 == f2, "floor(8*0.49) not NaN")
assertTrue(f3 == f3, "floor(9*0.5) not NaN")
assertEquals(f1, 4, "floor(8*0.5)")
assertEquals(f2, 3, "floor(8*0.49)")
assertEquals(f3, 4, "floor(9*0.5)")
end
-- math.tointeger
do
-- integers pass through unchanged
assertEquals(math.tointeger(0), 0, "tointeger(0)")
assertEquals(math.tointeger(42), 42, "tointeger(42)")
assertEquals(math.tointeger(-7), -7, "tointeger(-7)")
assertEquals(math.type(math.tointeger(1)), "integer", "tointeger result is integer")
-- float with whole value yields integer
assertEquals(math.tointeger(3.0), 3, "tointeger(3.0)")
assertEquals(math.tointeger(-1.0), -1, "tointeger(-1.0)")
assertEquals(math.type(math.tointeger(3.0)), "integer", "tointeger(3.0) type")
-- float with fractional part yields nil
assertEquals(math.tointeger(3.5), nil, "tointeger(3.5)")
assertEquals(math.tointeger(-0.1), nil, "tointeger(-0.1)")
-- string coercion: integer-valued string yields integer
assertEquals(math.tointeger("3"), 3, "tointeger(string int)")
assertEquals(math.tointeger("3.0"), 3, "tointeger(string float whole)")
assertEquals(math.tointeger("3.5"), nil, "tointeger(string float frac)")
assertEquals(math.tointeger("x"), nil, "tointeger(non-numeric string)")
-- nil yields nil
assertEquals(math.tointeger(nil), nil, "tointeger(nil)")
-- inf and nan yields nil
assertEquals(math.tointeger(1/0), nil, "tointeger(inf)")
assertEquals(math.tointeger(0/0), nil, "tointeger(nan)")
end
-- random and randomseed
do
-- random(): float in [0, 1)
for _ = 1, 20 do
local v = math.random()
assertTrue(v >= 0 and v < 1, "random() in [0,1)")
assertEquals(math.type(v), "float", "random() returns float")
end
-- random(n): integer in [1, n]
for _ = 1, 20 do
local v = math.random(10)
assertTrue(v >= 1 and v <= 10, "random(10) in [1,10]")
assertEquals(math.type(v), "integer", "random(n) returns integer")
end
-- random(m, n): integer in [m, n]
for _ = 1, 20 do
local v = math.random(5, 10)
assertTrue(v >= 5 and v <= 10, "random(5,10) in [5,10]")
assertEquals(math.type(v), "integer", "random(m,n) returns integer")
end
-- random(m, m): always returns m
for _ = 1, 5 do
assertEquals(math.random(7, 7), 7, "random(m,m) == m")
end
-- random(0): raw 64-bit integer, type integer
local r0 = math.random(0)
assertEquals(math.type(r0), "integer", "random(0) returns integer")
-- random(n) with n=1: always 1
for _ = 1, 5 do
assertEquals(math.random(1), 1, "random(1) == 1")
end
-- error: random(0.5) — non-integer arg1
assertError(function() math.random(0.5) end)
-- error: random(1, 0) — empty interval
assertError(function() math.random(1, 0) end)
-- error: random(0) is valid (raw), but random(-1) is not
assertError(function() math.random(-1) end)
-- error: random(1, 1.5) — non-integer arg2
assertError(function() math.random(1, 1.5) end)
-- randomseed arg validation
assertError(function() math.randomseed(0.5) end)
assertError(function() math.randomseed(1, 0.5) end)
assertError(function() math.randomseed(1/0) end)
end
-- randomseed: determinism
do
-- same seed yields identical sequence
math.randomseed(42)
local a1, a2, a3 = math.random(), math.random(100), math.random(1, 50)
math.randomseed(42)
local b1, b2, b3 = math.random(), math.random(100), math.random(1, 50)
assertEquals(a1, b1, "randomseed: float sequence reproducible")
assertEquals(a2, b2, "randomseed: random(n) reproducible")
assertEquals(a3, b3, "randomseed: random(m,n) reproducible")
-- different seeds yields different first values
math.randomseed(1)
local c1 = math.random()
math.randomseed(2)
local d1 = math.random()
assertTrue(c1 ~= d1, "different seeds give different values")
-- two-argument seed: same pair yields same sequence
math.randomseed(123, 456)
local e1, e2 = math.random(), math.random()
math.randomseed(123, 456)
local f1, f2 = math.random(), math.random()
assertEquals(e1, f1, "randomseed(x,y): first value reproducible")
assertEquals(e2, f2, "randomseed(x,y): second value reproducible")
-- two-argument seed: different second arg yields different sequence
math.randomseed(123, 456)
local g1 = math.random()
math.randomseed(123, 789)
local h1 = math.random()
assertTrue(g1 ~= h1, "randomseed(x,y1) ~= randomseed(x,y2)")
-- no-args seed does not error and produces valid range
math.randomseed()
local i1 = math.random()
assertTrue(i1 >= 0 and i1 < 1, "randomseed(): range valid after auto-seed")
-- return values: two integers (Lua 5.4 contract)
local s1, s2 = math.randomseed(99)
assertEquals(math.type(s1), "integer", "randomseed returns integer s1")
assertEquals(math.type(s2), "integer", "randomseed returns integer s2")
-- re-seeding restores determinism after auto-seed
math.randomseed(7)
local j1 = math.random()
math.randomseed(7)
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