Files
plainleaf/client/space_lua/arithmetic_test.lua
T

1744 lines
47 KiB
Lua

local function assertEquals(actual, expected, message)
if actual ~= expected then
error('Assertion failed: ' .. message)
end
end
local function assertThrows(msg_substr, fn)
local ok, err = pcall(fn)
if ok then
error('Assertion failed: expected error containing "'
.. msg_substr .. '"')
end
if type(err) ~= 'string' then
err = tostring(err)
end
if not string.find(err, msg_substr, 1, true) then
error('Assertion failed: expected error message to contain "'
.. msg_substr .. '", got: "' .. err .. '"')
end
end
-- 1. Integer vs float zero divisors
-- 1.1. Integer zeros collapse (no -0 for integers)
assertEquals(1/0 == 1/-0, true, 'int: 1/0 == 1/-0 (+Inf)')
assertEquals(-1/0 == 1/-0, false, 'int: -1/0 != 1/-0')
-- 1.2. Float zeros sign preservation
assertEquals(1/0.0 == 1/-0.0, false, 'float: +Inf != -Inf')
assertEquals(1/0.0 == -1/-0.0, true, 'float: 1/0.0 == -1/-0.0')
assertEquals(-1/0.0 == 1/-0.0, true, 'float: -1/0.0 == 1/-0.0')
-- 1.3. Basic division
assertEquals(5/2, 2.5, 'div: 5/2 == 2.5')
assertEquals(-5/2, -2.5, 'div: -5/2 == -2.5')
assertEquals(5/-2, -2.5, 'div: 5/-2 == -2.5')
assertEquals(-5/-2, 2.5, 'div: -5/-2 == 2.5')
-- 2. Unary minus literals and simple expressions
assertEquals(1/-(0) == 1/0, true, 'unary minus: int literal (+Inf)')
assertEquals(1/-(0.0) == -1/0.0, true, 'unary minus: float literal (-Inf)')
assertEquals(1/-(1-1) == 1/0, true, 'unary minus: int expr (+Inf)')
assertEquals(1/-(1.0-1.0) == -1/0.0, true, 'unary minus: float expr (-Inf)')
-- 2.1. Unary minus coercion and precedence with power
assertEquals(-2^2, -4, 'precedence: -2^2 == -(2^2)')
assertEquals((-2)^2, 4, 'precedence: (-2)^2 == 4')
-- 2.1.1. Unary minus with float-typed exponentiation results
do
local function mt(x)
return math.type(x)
end
assertEquals(mt(-("1.0" ^ 1)), "float", "math.type(-('1.0'^1)) => float")
assertEquals(mt(-"1.0" ^ 1), "float", "math.type(-'1.0'^1) => float")
assertEquals(mt(-("2.0" ^ 1)), "float", "math.type(-('2.0'^1)) => float")
assertEquals(mt(-("2.0" ^ 2)), "float", "math.type(-('2.0'^2)) => float")
end
-- 2.2. Unary minus uses table metadata for dynamic keys
local dyn_tbl = {}
local dyn_key = 'zf'
dyn_tbl[dyn_key] = 0.0
assertEquals(1/-(dyn_tbl[dyn_key]) == -1/0.0, true, 'unary minus: table dyn key float (-Inf)')
-- 2.3. Unary minus uses table metadata for property access
local prop_tbl = { zf = 0.0, zi = 0 }
assertEquals(1/-(prop_tbl.zf) == -1/0.0, true, 'unary minus: table prop float (-Inf)')
assertEquals(1/-(prop_tbl.zi) == 1/0, true, 'unary minus: table prop int (+Inf)')
-- 2.4. Unary minus uses env metadata for locals
local u_zi, u_zf, u_zfn = 0, 0.0, -0.0
assertEquals(1/-(u_zi) == 1/0, true, 'var: unary minus zi (+Inf)')
assertEquals(1/-(u_zfn) == 1/0.0, true, 'var: unary minus zfn (+Inf)')
assertEquals(1/u_zf == 1/0.0, true, 'var: zf (+Inf)')
-- 3. Integer operations (must not produce -0)
assertEquals(1/(1-1) == 1/0, true, 'int: sub (+0)')
assertEquals(1/(0*-1) == 1/0, true, 'int: mul (+0)')
assertEquals(1/(0%1) == 1/0, true, 'int: mod (+0)')
assertEquals(1/(0%-1) == 1/0, true, 'int: mod neg divisor (+0)')
-- 4. Float operations (must preserve -0.0)
assertEquals(1/(0.0*-1.0) == -1/ 0.0, true, 'float: mul (-0.0)')
assertEquals(1/((-0.0)%1.0) == -1/ 0.0, true, 'float: mod (-0.0)')
assertEquals(1/((-0.0)%-1.0) == -1/ 0.0, true, 'float: mod neg divisor (-0.0)')
-- 4.1. Zero result from float addition prefers +0.0
assertEquals(1/((-0.0)+0.0) == 1/0.0, true, 'float: (-0.0)+0.0 yields +0.0')
-- 4.2. Plain -0.0 handling in arithmetic
do
local nz = -0.0
-- Addition: -0.0 + 0 yields +0.0 (IEEE 754 rule)
assertEquals(1/(nz + 0), 1/0.0, 'plain -0.0: nz + 0 yields +0.0')
assertEquals(1/(0 + nz), 1/0.0, 'plain -0.0: 0 + nz yields +0.0')
-- Multiplication preserves -0.0
assertEquals(1/(nz * 1), -1/0.0, 'plain -0.0: nz * 1 yields -0.0')
assertEquals(1/(1 * nz), -1/0.0, 'plain -0.0: 1 * nz yields -0.0')
-- Subtraction: -0.0 - 0 = -0.0
assertEquals(1/(nz - 0), 1/0.0, 'plain -0.0: nz - 0 yields +0.0')
-- Subtraction: -0.0 - 0.0 = -0.0 (float)
assertEquals(1/(nz - 0.0), -1/0.0, 'plain -0.0: nz - 0.0 yields -0.0')
-- Subtraction: 0 - (-0.0) = +0.0
assertEquals(1/(0 - nz), 1/0.0, 'plain -0.0: 0 - nz yields +0.0')
end
-- 4.3. Expression-generated -0.0
do
local r = 0.0 * -1.0
assertEquals(1/r, -1/0.0, 'expr: 0.0 * -1.0 yields -0.0')
local s = -1.0 * 0.0
assertEquals(1/s, -1/0.0, 'expr: -1.0 * 0.0 yields -0.0')
-- Division producing -0.0
local d = -0.0 / 1.0
assertEquals(1/d, -1/0.0, 'expr: -0.0 / 1.0 yields -0.0')
end
-- 5. Mixed arithmetic producing zero
assertEquals(1/(0*-1.0) == -1/0.0, true, 'mixed: mul int*float (-0.0)')
assertEquals(1/(0.0*-1 ) == -1/0.0, true, 'mixed: mul float*int (-0.0)')
assertEquals(1/(1.0+(-1)) == 1/0.0, true, 'mixed: add (+0.0)')
assertEquals(1/(-(1-1.0)) == -1/0.0, true, 'mixed: sub then unary minus (-0.0)')
-- 5.1. Dynamic key table metadata affects binary ops
local dyn_t, dyn_k = {}, 'zf'
dyn_t[dyn_k] = 0.0
assertEquals(1/dyn_t[dyn_k] == 1/0.0, true, 'binary: table dyn key float (+Inf)')
-- 6. Variables
local zi, zf, zfn = 0, 0.0, -0.0
assertEquals(1/zi == 1/0, true, 'var: zi (+Inf)')
assertEquals(1/zf == 1/0.0, true, 'var: zf (+Inf)')
assertEquals(1/zfn == -1/0.0, true, 'var: zfn (-Inf)')
assertEquals(1/-(zi) == 1/0, true, 'var: unary minus zi (+Inf)')
assertEquals(1/-(zfn) == 1/0.0, true, 'var: unary minus zfn (+Inf)')
-- 6.1. Variables: metadata must flow through reassignment
local zswap = 0.0
assertEquals(1/zswap == 1/0.0, true, 'var: zswap float (+Inf)')
zswap = 0
assertEquals(1/zswap == 1/0, true, 'var: zswap reassigned int (+Inf)')
zswap = 0.0
assertEquals(1/zswap == 1/0.0, true, 'var: zswap reassigned float (+Inf)')
-- 7. Functions returning zeros and unary minus
local function ret_zi()
return 0
end
local function ret_zf()
return 0.0
end
local function ret_zfn()
return -0.0
end
assertEquals(1/ret_zi() == 1/0, true, 'fn: ret_zi (+Inf)')
assertEquals(1/ret_zf() == 1/0.0, true, 'fn: ret_zf (+Inf)')
assertEquals(1/ret_zfn() == -1/0.0, true, 'fn: ret_zfn (-Inf)')
assertEquals(1/-(ret_zi()) == 1/0, true, 'fn unary minus: ret_zi (+Inf)')
assertEquals(1/-(ret_zf()) == -1/0.0, true, 'fn unary minus: ret_zf (-Inf)')
assertEquals(1/-(ret_zfn()) == 1/0.0, true, 'fn unary minus: ret_zfn (+Inf)')
-- 8. Tables and arrays
local t, arr = {zi=zi, zfn=zfn}, {zi, zfn}
-- 8.1. Tables
assertEquals(1/t.zi == 1/0, true, 'table: t.zi (+Inf)')
assertEquals(1/t.zfn == -1/0.0, true, 'table: t.zfn (-Inf)')
-- 8.2. Arrays
assertEquals(1/arr[1] == 1/0, true, 'array: arr[1]=zi (+Inf)')
assertEquals(1/arr[2] == -1/0.0, true, 'array: arr[2]=zfn (-Inf)')
-- 8.3. Arrays: dynamic numeric index key metadata
local arr2 = {}
local idx = 1
arr2[idx] = 0.0
assertEquals(1/arr2[idx] == 1/0.0, true, 'array: dyn index float (+Inf)')
-- 9. Deeply nested parentheses and expressions
local xi, xf = 1-1, 1.0-1.0
local deepi = -((((0+0)-(1-1))+(zi-xi))) -- int path (+0)
local deepf = -((((0.0+0.0)-(1.0-1.0))+(zf-xf))) -- float path (-0.0)
assertEquals(1/deepi == 1/0, true, 'nested: int (+Inf)')
assertEquals(1/deepf == -1/0.0, true, 'nested: float (-Inf)')
-- 10. Floor division near zero
assertEquals(1/(0//1) == 1/0, true, 'floor div: int (+0)')
assertEquals(1/(0.0//1.0) == 1/0.0, true, 'floor div: float (+Inf)')
assertEquals(1/((-0.0)//1.0) == -1/0.0, true, 'floor div: float (-Inf)')
assertEquals(1/(0//1.0) == 1/0.0, true, 'floor div: mixed (+0.0)')
-- 10.1. Modulo/division identity
local function id_ok(a, b)
return a == b * (a // b) + a % b
end
assertEquals(id_ok(5, 2), true, 'identity: 5, 2')
assertEquals(id_ok(-5, 2), true, 'identity: -5, 2')
assertEquals(id_ok(5, -2), true, 'identity: 5, -2')
assertEquals(id_ok(-5, -2), true, 'identity: -5, -2')
-- 10.2. Floor division signs
assertEquals(5//-2, -3, 'idiv: 5//-2 == -3')
assertEquals(-5//2, -3, 'idiv: -5//2 == -3')
assertEquals(-5//-2, 2, 'idiv: -5//-2 == 2')
-- 11. Ordering and NaN
assertEquals((-0.0) < (0.0), false, 'ordering: -0.0 < 0.0 is false')
assertEquals((0.0) < (-0.0), false, 'ordering: 0.0 < -0.0 is false')
assertEquals((-0.0) <= (0.0), true, 'ordering: -0.0 <= 0.0')
assertEquals((0.0) <= (-0.0), true, 'ordering: 0.0 <= -0.0')
assertEquals((0/0) == (0/0), false, 'NaN: never equals itself')
-- 12. Bitwise operators
assertEquals((~0) == -1, true, 'bitwise not on int ok')
val = pcall(
function()
return ~0.0
end
)
assertEquals(val, true, 'bitwise not on float ok')
val = pcall(
function()
return 0<<1
end
)
assertEquals(val, true, 'shl int ok')
val = pcall(
function()
return 0.0<<1
end
)
assertEquals(val, true, 'shl float ok')
-- 12.1 Bitwise ops results
assertEquals((5&3) == 1, true, 'bitwise and result')
assertEquals((5|2) == 7, true, 'bitwise or result')
assertEquals((5~1) == 4, true, 'bitwise xor result')
assertEquals((1<<5) == 32, true, 'bitwise shl result')
assertEquals((32>>5) == 1, true, 'bitwise shr result')
-- 12.2 Bitwise with float values
assertEquals((~(-0.0)) == -1, true, 'bitwise not on -0.0 == -1')
-- 13. Evaluation order (left-to-right) for binary ops
local log, val
-- arithmetic + - * /
local function lhs_num()
log[#log + 1] = 'L'
return 1
end
local function rhs_num()
log[#log + 1] = 'R'
return 2
end
log = {}
val = lhs_num()+rhs_num()
assertEquals(table.concat(log, ''), 'LR', 'order: +')
log = {}
val = lhs_num()-rhs_num()
assertEquals(table.concat(log, ''), 'LR', 'order: -')
log = {}
val = lhs_num()*rhs_num()
assertEquals(table.concat(log, ''), 'LR', 'order: *')
log = {}
val = lhs_num()/rhs_num()
assertEquals(table.concat(log, ''), 'LR', 'order: /')
-- floor div and mod
log = {}
val = lhs_num()//rhs_num()
assertEquals(table.concat(log, ''), 'LR', 'order: //')
log = {}
val = lhs_num()%rhs_num()
assertEquals(table.concat(log, ''), 'LR', 'order: %')
-- power and concatenation
log = {}
val = lhs_num()^rhs_num()
assertEquals(table.concat(log, ''), 'LR', 'order: ^')
local function lhs_str()
log[#log + 1] = 'L'
return 'a'
end
local function rhs_str()
log[#log + 1] = 'R'
return 'b'
end
log = {}
val = lhs_str()..rhs_str()
assertEquals(table.concat(log, ''), 'LR', 'order: ..')
-- relational
log = {}
val = (lhs_num() < rhs_num())
assertEquals(table.concat(log, ''), 'LR', 'order: <')
log = {}
val = (lhs_num() <= rhs_num())
assertEquals(table.concat(log, ''), 'LR', 'order: <=')
log = {}
val = (lhs_num() > rhs_num())
assertEquals(table.concat(log, ''), 'LR', 'order: >')
log = {}
val = (lhs_num() >= rhs_num())
assertEquals(table.concat(log, ''), 'LR', 'order: >=')
log = {}
val = (lhs_num() == rhs_num())
assertEquals(table.concat(log, ''), 'LR', 'order: ==')
log = {}
val = (lhs_num() ~= rhs_num())
assertEquals(table.concat(log, ''), 'LR', 'order: ~=')
-- bitwise
log = {}
val = lhs_num()&rhs_num()
assertEquals(table.concat(log, ''), 'LR', 'order: &')
log = {}
val = lhs_num()|rhs_num()
assertEquals(table.concat(log, ''), 'LR', 'order: |')
log = {}
val = lhs_num()~rhs_num()
assertEquals(table.concat(log, ''), 'LR', 'order: ~ (xor)')
log = {}
val = lhs_num()<<rhs_num()
assertEquals(table.concat(log, ''), 'LR', 'order: <<')
log = {}
val = lhs_num()>>rhs_num()
assertEquals(table.concat(log, ''), 'LR', 'order: >>')
-- 13.2. Nested expressions and associativity
local function exp_a()
log[#log + 1] = 'A'
return 2
end
local function exp_b()
log[#log + 1] = 'B'
return 3
end
local function exp_c()
log[#log + 1] = 'C'
return 2
end
log = {}
val = exp_a() ^ (exp_b() ^ exp_c())
assertEquals(val, 512, 'nested expression result')
assertEquals(table.concat(log, ''), 'ABC', 'nested expression associativity')
assertEquals(2^3^2, 512, 'power associativity')
-- 14. String-to-number coercion around zero
assertEquals(1/('0') == 1/0.0, true, 'str: ("0") (+Inf)')
assertEquals(1/('-0') == 1/0.0, true, 'str: ("-0") (+Inf)')
assertEquals(1/('0.0') == 1/0.0, true, 'str: ("0.0") (+Inf)')
assertEquals(1/('-0.0') == -1/0.0, true, 'str: ("-0.0") (-Inf)')
assertEquals(1/-('0') == 1/0.0, true, 'str: unary minus -("0") (+Inf)')
assertEquals(1/-('-0') == 1/0.0, true, 'str: unary minus -("-0") (+Inf)')
-- 14.1. General arithmetic with numeric strings
assertEquals('1'+2, 3, 'str-num: "1"+2 == 3')
assertEquals(' -2 ' * '3', -6, 'str-num: " -2 " * "3" == -6')
assertEquals('0x10' + 1, 17, 'str-num: hex int string + 1 == 17')
assertEquals('0x1p4' + 0, 16, 'str-num: hex float string + 0 == 16')
assertThrows("attempt to add a 'string' with a 'number'",
function()
return 'x1'+1
end
)
-- 14.2 Numeric string coercion preserves int/float kind
do
local function mt(x)
return math.type(x)
end
assertEquals(mt("0" + 0), "integer", "string '0' + 0 => integer")
assertEquals(mt("-0" + 0), "integer", "string '-0' + 0 => integer")
assertEquals(mt("0.0" + 0), "float", "string '0.0' + 0 => float")
assertEquals(mt("-0.0" + 0), "float", "string '-0.0' + 0 => float")
assertEquals(mt("0" + 0.0), "float", "string '0' + 0.0 => float")
assertEquals(mt("0.0" + 0.0), "float", "string '0.0' + 0.0 => float")
assertEquals(mt("0" + -0), "integer", "string '0' + -0 => integer")
assertEquals(mt("0" + -0.0), "float", "string '0' + -0.0 => float")
-- Regression coverage: numeric strings for all arithmetic ops and operand order
assertEquals(mt("0" - 0), "integer", "string '0' - 0 => integer")
assertEquals(mt("0.0" - 0), "float", "string '0.0' - 0 => float")
assertEquals(mt("0" * 1), "integer", "string '0' * 1 => integer")
assertEquals(mt("0.0" * 1), "float", "string '0.0' * 1 => float")
assertEquals(mt("0" / 1), "float", "string '0' / 1 => float")
assertEquals(mt("0.0" / 1), "float", "string '0.0' / 1 => float")
assertEquals(mt("0" // 1), "integer", "string '0' // 1 => integer")
assertEquals(mt("0.0" // 1), "float", "string '0.0' // 1 => float")
assertEquals(mt("0" % 1), "integer", "string '0' % 1 => integer")
assertEquals(mt("0.0" % 1), "float", "string '0.0' % 1 => float")
assertEquals(mt("2" ^ 1), "float", "string '2' ^ 1 => float")
assertEquals(mt("2.0" ^ 1), "float", "string '2.0' ^ 1 => float")
assertEquals(mt(0 + "0"), "integer", "0 + string '0' => integer")
assertEquals(mt(0 + "0.0"), "float", "0 + string '0.0' => float")
assertEquals(mt(0 - "0"), "integer", "0 - string '0' => integer")
assertEquals(mt(0 - "0.0"), "float", "0 - string '0.0' => float")
assertEquals(mt(1 * "0"), "integer", "1 * string '0' => integer")
assertEquals(mt(1 * "0.0"), "float", "1 * string '0.0' => float")
assertEquals(mt(0 / "1"), "float", "0 / string '1' => float")
assertEquals(mt(0 / "1.0"), "float", "0 / string '1.0' => float")
assertEquals(mt(0 // "1"), "integer", "0 // string '1' => integer")
assertEquals(mt(0 // "1.0"), "float", "0 // string '1.0' => float")
assertEquals(mt(0 % "1"), "integer", "0 % string '1' => integer")
assertEquals(mt(0 % "1.0"), "float", "0 % string '1.0' => float")
assertEquals(mt(2 ^ "1"), "float", "2 ^ string '1' => float")
assertEquals(mt(2 ^ "1.0"), "float", "2 ^ string '1.0' => float")
assertEquals((1 // "2"), 0, "1 // '2' == 0")
assertEquals((1 // "2.0"), 0.0, "1 // '2.0' == 0.0")
end
-- 15. Recursive function producing int zero (and unary minus)
local function rec_zero(n)
if n == 0 then
return 0
end
return -rec_zero(n - 1)
end
assertEquals(1/rec_zero(5) == 1/0, true, 'recursive: rec_zero (+Inf)')
assertEquals(1/(rec_zero(5)) == 1/0, true, 'recursive: (rec_zero) (+Inf)')
assertEquals(1/-(rec_zero(5)) == 1/0, true, 'recursive: -(rec_zero) (+Inf)')
assertEquals(1/-rec_zero(5) == 1/0, true, 'recursive: -(rec_zero) (+Inf)')
-- 16. Modulo and integer division by zero
-- 16.1. Modulo by zero
assertThrows("attempt to perform 'n%0'",
function()
return 1%0
end
)
val = pcall(
function()
return 1.0%0.0
end
)
assertEquals(val, true, 'float mod by zero ok (NaN)')
val = pcall(
function()
return 1.0%0
end
)
assertEquals(val, true, 'mixed (float,int) mod by zero ok (NaN)')
val = pcall(
function()
return 1%0.0
end
)
assertEquals(val, true, 'mixed (int,float) mod by zero ok (NaN)')
-- 16.2. Integer division by zero
assertThrows('divide by zero',
function()
return 1//0
end
)
val = pcall(
function()
return 1.0//0.0
end
)
assertEquals(val, true, 'float idiv by zero ok (+Inf/-Inf)')
val = pcall(
function()
return 1.0//0
end
)
assertEquals(val, true, 'mixed (float,int) idiv by zero ok (+Inf/-Inf)')
val = pcall(
function()
return 1//0.0
end
)
assertEquals(val, true, 'mixed (int,float) idiv by zero ok (+Inf/-Inf)')
-- 16.3. Modulo sign semantics (explicit)
assertEquals(5 % -2, -1, 'mod: 5 % -2 == -1')
assertEquals(-5 % 2, 1, 'mod: -5 % 2 == 1')
assertEquals(-5 % -2, -1, 'mod: -5 % -2 == -1')
-- 17. Metamethod precedence: __add should dispatch
local mt = {
__add = function(_, _)
return 'added'
end
}
local tbl = setmetatable({}, mt)
assertEquals(tbl + tbl, 'added', '__add dispatched')
-- 17.1 Unary metamethod: __unm (negation)
local mt_unm = {
__unm = function(_)
return 'negated'
end
}
local u = setmetatable({}, mt_unm)
assertEquals(-u, 'negated', '__unm dispatched')
-- 17.2 Unary metamethod: __bnot (bitwise NOT)
local bnot_calls = 0
local mt_bnot = {
__bnot = function(_)
bnot_calls = bnot_calls + 1
return 123
end
}
local b = setmetatable({}, mt_bnot)
assertEquals(~b, 123, '__bnot dispatched')
assertEquals(bnot_calls, 1, '__bnot called exactly once')
-- 17.3. Unary metamethods multi-return (first return only)
local mt_unm_mr = {
__unm = function(_)
return 7, 8
end
}
local um = setmetatable({}, mt_unm_mr)
assertEquals(-um, 7, '__unm uses first return value')
local mt_bnot_mr = {
__bnot = function(_)
return 9, 10
end
}
local bm = setmetatable({}, mt_bnot_mr)
assertEquals(~bm, 9, '__bnot uses first return value')
-- 18. Multi-return in arithmetic (first return only)
local function multi_ret()
return 0, 1
end
assertEquals(1/(multi_ret()) == 1/0, true, 'multi-ret: 1st used (+Inf)')
assertEquals(1/-(multi_ret()) == 1/0, true, 'multi-ret: unary minus 1st used (+Inf)')
-- 18.1 Exponentiation zero edge cases
assertEquals(0^0 == 1, true, 'pow: 0^0 == 1')
assertEquals((-0.0)^0 == 1, true, 'pow: (-0.0)^0 == 1')
-- 19. Error tests
assertThrows('has no integer representation',
function()
return ~0.5
end
)
assertThrows("attempt to add a 'string' with a 'number'",
function()
return 'a'+1
end
)
assertThrows('attempt to perform arithmetic on a table value',
function()
return -{}
end
)
-- 19.1. Bitwise on non-integers should error
assertThrows('has no integer representation',
function()
return 1.5&1
end
)
assertThrows(
"attempt to perform bitwise operation on a string value (constant '3')",
function()
return '3'|1
end
)
assertThrows('has no integer representation',
function()
return 1~1.2
end
)
assertThrows('has no integer representation',
function()
return 1<<0.1
end
)
-- 19.2. Relational type error
assertThrows('attempt to compare number with string',
function()
return 1<'1'
end
)
-- 19.3. Additional negative tests
assertThrows('attempt to perform arithmetic on a table value',
function()
return 1+{}
end
)
assertThrows("attempt to unm a 'string' with a 'string'",
function()
return -'x'
end
)
assertThrows(
"attempt to perform bitwise operation on a string value (constant '1')",
function()
return ~'1'
end
)
assertThrows('attempt to compare string with number',
function()
return '1'<1
end
)
assertThrows('attempt to compare number with table',
function()
return 1<{}
end
)
-- 19.4. String arithmetic: exact verb mapping
assertThrows("attempt to sub a 'string' with a 'number'",
function()
return 'x' - 1
end
)
assertThrows("attempt to mul a 'string' with a 'number'",
function()
return 'x' * 2
end
)
assertThrows("attempt to div a 'string' with a 'number'",
function()
return 'x' / 2
end
)
assertThrows("attempt to idiv a 'string' with a 'number'",
function()
return 'x' // 2
end
)
assertThrows("attempt to mod a 'string' with a 'number'",
function()
return 'x' % 2
end
)
assertThrows("attempt to pow a 'string' with a 'number'",
function()
return 'x' ^ 2
end
)
-- 19.4.1. String arithmetic: type pairing and string-vs-string cases
assertThrows("attempt to add a 'string' with a 'string'",
function()
return 'x' + 'y'
end
)
assertThrows("attempt to mul a 'number' with a 'string'",
function()
return 2 * 'x'
end
)
-- 19.5. Bitwise: exact type errors
assertThrows("attempt to perform bitwise operation on a table value",
function()
return {} & 1
end
)
assertThrows("attempt to perform bitwise operation on a nil value",
function()
return nil | 1
end
)
assertThrows("attempt to perform bitwise operation on a boolean value",
function()
return true ~ 1
end
)
-- 19.6. Bitwise shifts: non-integer RHS
assertThrows("number has no integer representation",
function()
return 1 << 0.5
end
)
assertThrows("number has no integer representation",
function()
return 8 >> 0.25
end
)
-- 19.7. Concatenation: exact messages
assertThrows("attempt to concatenate a nil value",
function()
return nil .. "x"
end
)
assertThrows("attempt to concatenate a nil value",
function()
return "x" .. nil
end
)
assertThrows("attempt to concatenate a table value",
function()
return "x" .. {}
end
)
-- 19.8. Length operator: exact message
assertThrows("attempt to get length of a number value",
function()
return #1
end
)
assertThrows("attempt to get length of a nil value",
function()
return #nil
end
)
-- 19.9. Relational mismatches
assertThrows("attempt to compare number with string",
function()
return 1 <= '1'
end
)
assertThrows("attempt to compare number with string",
function()
return '1' >= 1
end
)
assertThrows("attempt to compare table with number",
function()
return 1 > {}
end
)
assertThrows("attempt to compare table with number",
function()
return {} < 1
end
)
-- 19.10. Explicit int-path division/modulo by zero through expr
assertThrows("attempt to perform 'n%0'",
function()
return 1 % (1-1)
end
)
assertThrows("attempt to divide by zero",
function()
return 1 // (1-1)
end
)
-- 20. `tostring()` numeric formatting
-- 20.1. Integers
assertEquals(tostring(0), '0', 'tostring: 0')
assertEquals(tostring(1), '1', 'tostring: 1')
assertEquals(tostring(-5), '-5', 'tostring: -5')
assertEquals(tostring(42), '42', 'tostring: 42')
assertEquals(tostring(-123), '-123', 'tostring: -123')
-- 20.2. Float zeros (positive and negative)
assertEquals(tostring(0.0), '0.0', 'tostring: 0.0')
assertEquals(tostring(-0.0), '-0.0', 'tostring: -0.0')
-- 20.3. Integer-valued floats
assertEquals(tostring(1.0), '1.0', 'tostring: 1.0')
assertEquals(tostring(2.0), '2.0', 'tostring: 2.0')
assertEquals(tostring(-3.0), '-3.0', 'tostring: -3.0')
assertEquals(tostring(100.0), '100.0', 'tostring: 100.0')
-- 20.4. Non-integer floats
assertEquals(tostring(0.5), '0.5', 'tostring: 0.5')
assertEquals(tostring(3.14), '3.14', 'tostring: 3.14')
assertEquals(tostring(-2.5), '-2.5', 'tostring: -2.5')
-- 20.5. Special float values
assertEquals(tostring(1/0.0), 'inf', 'tostring: +inf')
assertEquals(tostring(-1/0.0), '-inf', 'tostring: -inf')
assertEquals(tostring(0.0/0.0), '-nan', 'tostring: NaN')
assertEquals(tostring(1.0%0.0), '-nan', 'tostring: NaN from modulo')
assertEquals(tostring((-1.0)%0.0), '-nan', 'tostring: NaN from neg modulo')
-- 20.6. Results from tonumber() preserve type
assertEquals(tostring(tonumber('5')), '5', 'tonumber int: "5"')
assertEquals(tostring(tonumber('5.')), '5.0', 'tonumber float: "5."')
assertEquals(tostring(tonumber('.5')), '0.5', 'tonumber float: ".5"')
assertEquals(tostring(tonumber('5.0')), '5.0', 'tonumber float: "5.0"')
assertEquals(tostring(tonumber('0x10')), '16', 'tonumber hex int: "0x10"')
assertEquals(tostring(tonumber('0x10.0')), '16.0', 'tonumber hex float: "0x10.0"')
assertEquals(tostring(tonumber('0x1p4')), '16.0', 'tonumber hex float: "0x1p4"')
-- 20.7. Arithmetic results formatting
assertEquals(tostring(1+1), '2', 'int+int yields int')
assertEquals(tostring(1.0+1.0), '2.0', 'float+float yields float')
assertEquals(tostring(1+1.0), '2.0', 'int+float yields float')
assertEquals(tostring(5/2), '2.5', 'division yields float')
assertEquals(tostring(4/2), '2.0', 'division exact yields float')
-- 21. Numeric for loops: comprehensive type/mode coverage
-- 21.1. Integer mode (all params integer-valued and untagged)
do
local types, vals = {}, {}
for i = 1, 3 do
table.insert(types, math.type(i))
table.insert(vals, tostring(i))
end
assertEquals(table.concat(types, ','), 'integer,integer,integer', 'for 1,3: types')
assertEquals(table.concat(vals, ','), '1,2,3', 'for 1,3: values')
end
do
local types = {}
for i = 1, 3, 1 do
table.insert(types, math.type(i))
end
assertEquals(types[1], 'integer', 'for 1,3,1: type')
end
do
local vals = {}
for i = 3, 1, -1 do
table.insert(vals, tostring(i))
end
assertEquals(table.concat(vals, ','), '3,2,1', 'for 3,1,-1: descending')
end
do
local found_zero = false
local zero_str
for i = -1, 1 do
if i == 0 then
found_zero = true
zero_str = tostring(i)
end
end
assertEquals(found_zero, true, 'for -1,1: crosses zero')
assertEquals(zero_str, '0', 'for -1,1: zero is int')
end
do
for i = 1, 3 do
local zero = i - i
assertEquals(math.type(zero), 'integer', 'for 1,3: i-i is int')
assertEquals(tostring(zero), '0', 'for 1,3: i-i formats as int')
end
end
do
for i = 2, 4 do
local expr = i * 2 - i - i
assertEquals(math.type(expr), 'integer', 'for 2,4: int expr is int')
assertEquals(expr, 0, 'for 2,4: int expr value')
end
end
-- 21.2. Float mode (all params float-typed)
do
local types, vals = {}, {}
for i = 1.0, 3.0, 1.0 do
table.insert(types, math.type(i))
table.insert(vals, tostring(i))
end
assertEquals(table.concat(types, ','), 'float,float,float', 'for 1.0,3.0,1.0: types')
assertEquals(table.concat(vals, ','), '1.0,2.0,3.0', 'for 1.0,3.0,1.0: values')
end
do
local vals = {}
for i = 0.5, 2.5, 0.5 do
table.insert(vals, tostring(i))
end
assertEquals(table.concat(vals, ','), '0.5,1.0,1.5,2.0,2.5', 'for 0.5,2.5,0.5: values')
end
do
local vals = {}
for i = 3.0, 1.0, -1.0 do
table.insert(vals, tostring(i))
end
assertEquals(table.concat(vals, ','), '3.0,2.0,1.0', 'for 3.0,1.0,-1.0: descending')
end
do
local vals = {}
for i = 2.5, 0.5, -0.5 do
table.insert(vals, tostring(i))
end
assertEquals(table.concat(vals, ','), '2.5,2.0,1.5,1.0,0.5', 'for 2.5,0.5,-0.5: values')
end
do
local zero_str
for i = -1.0, 1.0 do
if i == 0 then
zero_str = tostring(i)
end
end
assertEquals(zero_str, '0.0', 'for -1.0,1.0: zero is float')
end
do
for i = 1.0, 3.0 do
local zero = i - i
assertEquals(math.type(zero), 'float', 'for 1.0,3.0: i-i is float')
assertEquals(tostring(zero), '0.0', 'for 1.0,3.0: i-i formats as float')
end
end
do
for i = 2.0, 4.0 do
local expr = i * 2.0 - i - i
assertEquals(math.type(expr), 'float', 'for 2.0,4.0: float expr is float')
assertEquals(tostring(expr), '0.0', 'for 2.0,4.0: float expr formats')
end
end
-- 21.3. Mixed mode (start type determines loop var type)
do
local types, vals = {}, {}
for i = 1, 3.5 do
table.insert(types, math.type(i))
table.insert(vals, tostring(i))
end
assertEquals(types[1], 'integer', 'for 1,3.5: start int yields var int')
assertEquals(types[2], 'integer', 'for 1,3.5: var stays int')
assertEquals(table.concat(vals, ','), '1,2,3', 'for 1,3.5: int formatting')
end
do
local types, vals = {}, {}
for i = 1.0, 3 do
table.insert(types, math.type(i))
table.insert(vals, tostring(i))
end
assertEquals(types[1], 'float', 'for 1.0,3: start float yields var float')
assertEquals(types[2], 'float', 'for 1.0,3: var stays float')
assertEquals(table.concat(vals, ','), '1.0,2.0,3.0', 'for 1.0,3: float formatting')
end
do
local types = {}
for i = 1, 3.0 do
table.insert(types, math.type(i))
end
assertEquals(types[1], 'integer', 'for 1,3.0: end=3.0 but start int yields var int')
end
do
local types, vals = {}, {}
for i = 1, 3, 1.0 do
table.insert(types, math.type(i))
table.insert(vals, tostring(i))
end
assertEquals(types[1], 'float', 'for 1,3,1.0: step float yields var float')
assertEquals(table.concat(vals, ','), '1.0,2.0,3.0', 'for 1,3,1.0: float formatting')
end
do
local types, vals = {}, {}
for i = 1.0, 3.0, 1 do
table.insert(types, math.type(i))
table.insert(vals, tostring(i))
end
assertEquals(types[1], 'float', 'for 1.0,3.0,1: start float yields var float')
assertEquals(table.concat(vals, ','), '1.0,2.0,3.0', 'for 1.0,3.0,1: float formatting')
end
do
local types = {}
for i = 3.0, 1, -1 do
table.insert(types, math.type(i))
end
assertEquals(types[1], 'float', 'for 3.0,1,-1: start float yields var float')
end
-- Mixed mode: float step, integer start
do
local types, vals = {}, {}
for i = 1, 3, 1.0 do
table.insert(types, math.type(i))
table.insert(vals, tostring(i))
end
assertEquals(types[1], 'float', 'for 1,3,1.0: step float yields var float')
assertEquals(table.concat(vals, ','), '1.0,2.0,3.0', 'for 1,3,1.0: float formatting')
end
-- Mixed mode: integer start, negative float step
do
local types, vals = {}, {}
for i = 3, 1, -1.0 do
table.insert(types, math.type(i))
table.insert(vals, tostring(i))
end
assertEquals(types[1], 'float', 'for 3,1,-1.0: step float yields var float')
assertEquals(table.concat(vals, ','), '3.0,2.0,1.0', 'for 3,1,-1.0: float formatting')
end
do
for i = 1, 3.5 do
local zero = i - i
assertEquals(math.type(zero), 'integer', 'for 1,3.5: start int yields i-i is int')
end
end
do
for i = 1.0, 3 do
local zero = i - i
assertEquals(math.type(zero), 'float', 'for 1.0,3: start float yields i-i is float')
end
end
-- 21.4. Boundary conditions
do
local count = 0
for i = 5, 5 do
count = count + 1
assertEquals(i, 5, 'for 5,5: i=5')
end
assertEquals(count, 1, 'for 5,5: one iteration')
end
do
local count = 0
for i = 5, 3 do
count = count + 1
end
assertEquals(count, 0, 'for 5,3: no iterations')
end
do
local count = 0
for i = 3, 5, -1 do
count = count + 1
end
assertEquals(count, 0, 'for 3,5,-1: no iterations')
end
do
local last
for i = 1.0, 3.0 do
last = i
end
assertEquals(tostring(last), '3.0', 'for 1.0,3.0: exactly reaches end')
end
do
local last
for i = 1, 10, 3 do
last = i
end
assertEquals(last, 10, 'for 1,10,3: reaches end exactly')
end
do
local last
for i = 1, 9, 3 do
last = i
end
assertEquals(last, 7, 'for 1,9,3: stops before end')
end
do
local vals = {}
for i = 0.1, 0.3, 0.1 do
table.insert(vals, i)
end
assertEquals(#vals, 2, 'for 0.1,0.3,0.1: small float step iterations')
end
do
local vals = {}
-- 0.5 has exact binary representation
for i = 0.5, 1.5, 0.5 do
table.insert(vals, i)
end
assertEquals(#vals, 3, 'for 0.5,1.5,0.5: small float step')
end
-- 21.5. Edge cases and errors
assertThrows('step is zero', function()
for i = 1, 10, 0 do
end
end)
assertThrows('step is zero', function()
for i = 1.0, 10.0, 0.0 do
end
end)
do
local count = 0
for i = 1, 1000000 do
count = count + 1
if count > 5 then break end
end
assertEquals(count, 6, 'for 1,1000000: large range with break')
end
do
local sum = 0
for i = 1, 5, 2 do
sum = sum + i
end
assertEquals(sum, 9, 'for 1,5,2: step>1 sum (1+3+5)')
end
do
local sum = 0
for i = 10, 1, -3 do
sum = sum + i
end
assertEquals(sum, 22, 'for 10,1,-3: negative step>1 sum (10+7+4+1)')
end
-- 22. Table numeric key equivalence
-- 22.1. Integer-valued floats normalize to integers
do
local t = {}
t[1] = 'one'
assertEquals(t[1.0], 'one', 'table: t[1] accessed via t[1.0]')
assertEquals(t[1], 'one', 'table: t[1] accessed via t[1]')
end
do
local t = {}
t[2.0] = 'two'
assertEquals(t[2], 'two', 'table: t[2.0] accessed via t[2]')
assertEquals(t[2.0], 'two', 'table: t[2.0] accessed via t[2.0]')
end
do
local t = {}
t[1] = 'a'
t[2.0] = 'b'
t[3] = 'c'
assertEquals(t[1.0], 'a', 'table: multi-key t[1.0]')
assertEquals(t[2], 'b', 'table: multi-key t[2]')
assertEquals(t[3.0], 'c', 'table: multi-key t[3.0]')
end
do
local t = {}
t[5] = 'first'
t[5.0] = 'second'
assertEquals(t[5], 'second', 'table: overwrite int with float')
assertEquals(t[5.0], 'second', 'table: overwrite int with float (access)')
end
do
local t = {}
t[100] = 'hundred'
assertEquals(t[100.0], 'hundred', 'table: large int t[100.0]')
end
-- 22.2. Zero normalization (both -0 and +0 map to same key)
do
local t = {}
t[0] = 'zero'
assertEquals(t[-0.0], 'zero', 'table: t[0] accessed via t[-0.0]')
assertEquals(t[0.0], 'zero', 'table: t[0] accessed via t[0.0]')
assertEquals(t[0], 'zero', 'table: t[0] accessed via t[0]')
end
do
local t = {}
t[-0.0] = 'negzero'
assertEquals(t[0], 'negzero', 'table: t[-0.0] accessed via t[0]')
assertEquals(t[0.0], 'negzero', 'table: t[-0.0] accessed via t[0.0]')
assertEquals(t[-0.0], 'negzero', 'table: t[-0.0] accessed via t[-0.0]')
end
do
local t = {}
t[0.0] = 'float_zero'
assertEquals(t[0], 'float_zero', 'table: t[0.0] accessed via t[0]')
assertEquals(t[-0.0], 'float_zero', 'table: t[0.0] accessed via t[-0.0]')
end
do
local t = {}
t[0] = 'first'
t[-0.0] = 'second'
t[0.0] = 'third'
assertEquals(t[0], 'third', 'table: zero overwrite final')
assertEquals(t[-0.0], 'third', 'table: zero overwrite via -0.0')
end
-- 22.3. Non-integer floats are distinct keys
do
local t = {}
t[1] = 'int_one'
t[1.5] = 'one_point_five'
assertEquals(t[1], 'int_one', 'table: non-int float t[1]')
assertEquals(t[1.5], 'one_point_five', 'table: non-int float t[1.5]')
assertEquals(t[1.0], 'int_one', 'table: non-int float t[1.0] maps to int')
end
do
local t = {}
t[0.5] = 'half'
t[1.5] = 'one_half'
t[2.5] = 'two_half'
assertEquals(t[0.5], 'half', 'table: multi non-int t[0.5]')
assertEquals(t[1.5], 'one_half', 'table: multi non-int t[1.5]')
assertEquals(t[2.5], 'two_half', 'table: multi non-int t[2.5]')
end
-- 22.4. Mixed integer and float keys
do
local t = {}
t[0] = 'zero'
t[1] = 'one'
t[1.5] = 'one_point_five'
t[2.0] = 'two'
t[-0.0] = 'neg_zero'
assertEquals(t[0], 'neg_zero', 'table: mixed t[0] (last zero)')
assertEquals(t[0.0], 'neg_zero', 'table: mixed t[0.0]')
assertEquals(t[-0.0], 'neg_zero', 'table: mixed t[-0.0]')
assertEquals(t[1], 'one', 'table: mixed t[1]')
assertEquals(t[1.0], 'one', 'table: mixed t[1.0]')
assertEquals(t[1.5], 'one_point_five', 'table: mixed t[1.5]')
assertEquals(t[2], 'two', 'table: mixed t[2]')
assertEquals(t[2.0], 'two', 'table: mixed t[2.0]')
end
-- 22.5. Key equivalence with expressions
do
local t = {}
t[1+1] = 'two'
assertEquals(t[2.0], 'two', 'table: expr key t[1+1] via t[2.0]')
assertEquals(t[4/2], 'two', 'table: expr key t[4/2]')
end
do
local t = {}
t[1-1] = 'int_zero'
assertEquals(t[0], 'int_zero', 'table: expr t[1-1] via t[0]')
assertEquals(t[0.0], 'int_zero', 'table: expr t[1-1] via t[0.0]')
assertEquals(t[-0.0], 'int_zero', 'table: expr t[1-1] via t[-0.0]')
end
do
local t = {}
t[1.0-1.0] = 'float_zero'
assertEquals(t[0], 'float_zero', 'table: expr t[1.0-1.0] via t[0]')
assertEquals(t[-0.0], 'float_zero', 'table: expr t[1.0-1.0] via t[-0.0]')
end
-- 22.6. Key equivalence with variables
do
local t = {}
local i = 1
local f = 1.0
t[i] = 'from_int'
assertEquals(t[f], 'from_int', 'table: var int key via float var')
end
do
local t = {}
local i = 1
local f = 1.0
t[f] = 'from_float'
assertEquals(t[i], 'from_float', 'table: var float key via int var')
end
do
local t = {}
local zi = 0
local zf = 0.0
local zfn = -0.0
t[zi] = 'int_zero'
assertEquals(t[zf], 'int_zero', 'table: var zi via zf')
assertEquals(t[zfn], 'int_zero', 'table: var zi via zfn')
end
-- 22.7. Key equivalence in array part
do
local t = {10, 20, 30}
assertEquals(t[1.0], 10, 'table: array t[1.0]')
assertEquals(t[2.0], 20, 'table: array t[2.0]')
assertEquals(t[3.0], 30, 'table: array t[3.0]')
end
do
local t = {}
t[1.0] = 'first'
t[2.0] = 'second'
assertEquals(t[1], 'first', 'table: array assign t[1.0] via t[1]')
assertEquals(t[2], 'second', 'table: array assign t[2.0] via t[2]')
end
-- 22.8. Key counting and iteration
do
local t = {}
t[1] = 'a'
t[1.0] = 'b'
t[2] = 'c'
t[2.0] = 'd'
local count = 0
for k, v in pairs(t) do
count = count + 1
end
assertEquals(count, 2, 'table: normalized keys count as one')
end
do
local t = {}
t[0] = 'a'
t[0.0] = 'b'
t[-0.0] = 'c'
local count = 0
for k, v in pairs(t) do
count = count + 1
end
assertEquals(count, 1, 'table: all zeros count as one key')
end
-- 23. Type introspection: type() and math.type()
-- 23.1. Basic type() function (returns general Lua types)
assertEquals(type(nil), 'nil', 'type(nil)')
assertEquals(type(true), 'boolean', 'type(true)')
assertEquals(type(false), 'boolean', 'type(false)')
assertEquals(type(0), 'number', 'type(0) is number')
assertEquals(type(1), 'number', 'type(1) is number')
assertEquals(type(0.0), 'number', 'type(0.0) is number')
assertEquals(type(1.0), 'number', 'type(1.0) is number')
assertEquals(type(-0.0), 'number', 'type(-0.0) is number')
assertEquals(type(1.5), 'number', 'type(1.5) is number')
assertEquals(type(1/0.0), 'number', 'type(inf) is number')
assertEquals(type(0.0/0.0), 'number', 'type(nan) is number')
assertEquals(type('hello'), 'string', 'type(string)')
assertEquals(type({}), 'table', 'type(table)')
assertEquals(type(function() end), 'function', 'type(function)')
-- 23.2. math.type() function (distinguishes integer vs float)
-- Integers
assertEquals(math.type(0), 'integer', 'math.type(0)')
assertEquals(math.type(1), 'integer', 'math.type(1)')
assertEquals(math.type(-1), 'integer', 'math.type(-1)')
assertEquals(math.type(42), 'integer', 'math.type(42)')
assertEquals(math.type(-123), 'integer', 'math.type(-123)')
assertEquals(math.type(1000000), 'integer', 'math.type(1000000)')
-- Floats (literals with decimal point)
assertEquals(math.type(0.0), 'float', 'math.type(0.0)')
assertEquals(math.type(-0.0), 'float', 'math.type(-0.0)')
assertEquals(math.type(1.0), 'float', 'math.type(1.0)')
assertEquals(math.type(2.0), 'float', 'math.type(2.0)')
assertEquals(math.type(-3.0), 'float', 'math.type(-3.0)')
assertEquals(math.type(100.0), 'float', 'math.type(100.0)')
-- Floats (non-integer values)
assertEquals(math.type(0.5), 'float', 'math.type(0.5)')
assertEquals(math.type(1.5), 'float', 'math.type(1.5)')
assertEquals(math.type(3.14), 'float', 'math.type(3.14)')
assertEquals(math.type(-2.5), 'float', 'math.type(-2.5)')
-- Special float values
assertEquals(math.type(1/0.0), 'float', 'math.type(inf)')
assertEquals(math.type(-1/0.0), 'float', 'math.type(-inf)')
assertEquals(math.type(0.0/0.0), 'float', 'math.type(nan)')
-- Non-numbers return nil
assertEquals(math.type(nil), nil, 'math.type(nil) yields nil')
assertEquals(math.type(true), nil, 'math.type(boolean) yields nil')
assertEquals(math.type('123'), nil, 'math.type(string) yields nil')
assertEquals(math.type({}), nil, 'math.type(table) yields nil')
assertEquals(math.type(function() end), nil, 'math.type(function) yields nil')
-- 23.3. Integer arithmetic preserves integer type
do
local a = 1
local b = 2
assertEquals(math.type(a + b), 'integer', 'int + int yields integer')
assertEquals(math.type(a - b), 'integer', 'int - int yields integer')
assertEquals(math.type(a * b), 'integer', 'int * int yields integer')
assertEquals(math.type(a // b), 'integer', 'int // int yields integer')
assertEquals(math.type(a % b), 'integer', 'int % int yields integer')
end
-- Integer operations producing zero
do
assertEquals(math.type(1 - 1), 'integer', '1 - 1 yields integer zero')
assertEquals(math.type(0 * 5), 'integer', '0 * 5 yields integer zero')
assertEquals(math.type(0 % 1), 'integer', '0 % 1 yields integer zero')
assertEquals(math.type(0 // 1), 'integer', '0 // 1 yields integer zero')
end
-- 23.4. Float arithmetic preserves float type
do
local a = 1.0
local b = 2.0
assertEquals(math.type(a + b), 'float', 'float + float yields float')
assertEquals(math.type(a - b), 'float', 'float - float yields float')
assertEquals(math.type(a * b), 'float', 'float * float yields float')
assertEquals(math.type(a / b), 'float', 'float / float yields float')
assertEquals(math.type(a // b), 'float', 'float // float yields float')
assertEquals(math.type(a % b), 'float', 'float % float yields float')
end
-- Float operations producing zero
do
assertEquals(math.type(1.0 - 1.0), 'float', '1.0 - 1.0 yields float zero')
assertEquals(math.type(0.0 * 5.0), 'float', '0.0 * 5.0 yields float zero')
assertEquals(math.type(0.0 % 1.0), 'float', '0.0 % 1.0 yields float zero')
assertEquals(math.type(0.0 // 1.0), 'float', '0.0 // 1.0 yields float zero')
end
-- 23.5. Mixed arithmetic promotes to float
do
assertEquals(math.type(1 + 1.0), 'float', 'int + float yields float')
assertEquals(math.type(1.0 + 1), 'float', 'float + int yields float')
assertEquals(math.type(2 * 1.5), 'float', 'int * float yields float')
assertEquals(math.type(3.0 - 1), 'float', 'float - int yields float')
assertEquals(math.type(5 // 2.0), 'float', 'int // float yields float')
assertEquals(math.type(5.0 % 2), 'float', 'float % int yields float')
end
-- Mixed operations producing zero
do
assertEquals(math.type(1 - 1.0), 'float', '1 - 1.0 yields float zero')
assertEquals(math.type(0 * 1.0), 'float', '0 * 1.0 yields float zero')
assertEquals(math.type(0.0 * 1), 'float', '0.0 * 1 yields float zero')
end
-- 23.6. Division and power always produce floats
do
assertEquals(math.type(4 / 2), 'float', '4 / 2 yields float (2.0)')
assertEquals(math.type(5 / 2), 'float', '5 / 2 yields float (2.5)')
assertEquals(math.type(1 / 1), 'float', '1 / 1 yields float (1.0)')
assertEquals(math.type(2 ^ 3), 'float', '2 ^ 3 yields float (8.0)')
assertEquals(math.type(2 ^ 0), 'float', '2 ^ 0 yields float (1.0)')
assertEquals(math.type(10 ^ 2), 'float', '10 ^ 2 yields float (100.0)')
end
-- Division producing zero
do
assertEquals(math.type(0 / 1), 'float', '0 / 1 yields float zero')
assertEquals(math.type(0.0 / 1.0), 'float', '0.0 / 1.0 yields float zero')
end
-- 23.7. Unary minus preserves type
do
assertEquals(math.type(-5), 'integer', '-5 is integer')
assertEquals(math.type(-0), 'integer', '-0 is integer')
assertEquals(math.type(-5.0), 'float', '-5.0 is float')
assertEquals(math.type(-0.0), 'float', '-0.0 is float')
end
-- Unary minus on variables
do
local i = 5
local f = 5.0
assertEquals(math.type(-i), 'integer', 'unary minus int var')
assertEquals(math.type(-f), 'float', 'unary minus float var')
end
-- Unary minus on expressions
do
assertEquals(math.type(-(1 + 1)), 'integer', 'unary minus int expr')
assertEquals(math.type(-(1.0 + 1.0)), 'float', 'unary minus float expr')
end
-- 23.8. String coercion preserves type (tonumber)
do
assertEquals(math.type(tonumber('5')), 'integer', 'tonumber("5") yields integer')
assertEquals(math.type(tonumber('5.')), 'float', 'tonumber("5.") yields float')
assertEquals(math.type(tonumber('.5')), 'float', 'tonumber(".5") yields float')
assertEquals(math.type(tonumber('5.0')), 'float', 'tonumber("5.0") yields float')
assertEquals(math.type(tonumber('-0')), 'integer', 'tonumber("-0") yields integer')
assertEquals(math.type(tonumber('-0.0')), 'float', 'tonumber("-0.0") yields float')
end
-- Hexadecimal literals
do
assertEquals(math.type(tonumber('0x10')), 'integer', 'tonumber("0x10") yields integer')
assertEquals(math.type(tonumber('0x10.0')), 'float', 'tonumber("0x10.0") yields float')
assertEquals(math.type(tonumber('0x1p4')), 'float', 'tonumber("0x1p4") yields float')
end
-- 23.9. Variables preserve type through assignment
do
local i = 5
local f = 5.0
assertEquals(math.type(i), 'integer', 'int variable')
assertEquals(math.type(f), 'float', 'float variable')
local i2 = i
local f2 = f
assertEquals(math.type(i2), 'integer', 'int variable copy')
assertEquals(math.type(f2), 'float', 'float variable copy')
end
-- 23.10. Function returns preserve type
do
local function ret_int()
return 42
end
local function ret_float()
return 42.0
end
assertEquals(math.type(ret_int()), 'integer', 'function returns integer')
assertEquals(math.type(ret_float()), 'float', 'function returns float')
end
-- 23.11. Table values preserve type
do
local t = {
i = 10,
f = 10.0,
[1] = 20,
[2] = 20.0
}
assertEquals(math.type(t.i), 'integer', 'table int value (string key)')
assertEquals(math.type(t.f), 'float', 'table float value (string key)')
assertEquals(math.type(t[1]), 'integer', 'table int value (int key)')
assertEquals(math.type(t[2]), 'float', 'table float value (int key)')
end
-- 23.12. For-loop variable type tracking
do
-- Integer loop
for i = 1, 3 do
assertEquals(math.type(i), 'integer', 'for 1,3: var is integer')
break -- Just test first iteration
end
-- Float loop (start is float)
for i = 1.0, 3 do
assertEquals(math.type(i), 'float', 'for 1.0,3: var is float')
break
end
-- Float loop (step is float)
for i = 1, 3, 1.0 do
assertEquals(math.type(i), 'float', 'for 1,3,1.0: var is float')
break
end
end
-- 23.13. Bitwise operations require integers (input & output)
do
assertEquals(math.type(5 & 3), 'integer', 'bitwise and yields integer')
assertEquals(math.type(5 | 2), 'integer', 'bitwise or yields integer')
assertEquals(math.type(5 ~ 1), 'integer', 'bitwise xor yields integer')
assertEquals(math.type(1 << 5), 'integer', 'left shift yields integer')
assertEquals(math.type(32 >> 2), 'integer', 'right shift yields integer')
assertEquals(math.type(~0), 'integer', 'bitwise not yields integer')
end
-- Bitwise operations convert float operands to integer
do
assertEquals(math.type(5.0 & 3), 'integer', 'float & int yields integer')
assertEquals(math.type(5 | 2.0), 'integer', 'int | float yields integer')
assertEquals(math.type(5.0 ~ 1.0), 'integer', 'float ~ float yields integer')
end
-- 23.14. Edge cases: zero types
do
-- Positive integer zero
assertEquals(math.type(0), 'integer', '0 is integer')
assertEquals(math.type(1 - 1), 'integer', '1 - 1 is integer')
assertEquals(math.type(0 * 1), 'integer', '0 * 1 is integer')
-- Positive float zero
assertEquals(math.type(0.0), 'float', '0.0 is float')
assertEquals(math.type(1.0 - 1.0), 'float', '1.0 - 1.0 is float')
-- Negative float zero
assertEquals(math.type(-0.0), 'float', '-0.0 is float')
assertEquals(math.type(0.0 * -1.0), 'float', '0.0 * -1.0 is float')
-- Integer operations never produce -0
assertEquals(math.type(0 * -1), 'integer', '0 * -1 is integer (not -0)')
end
-- 23.15. Special float values
do
local inf = 1.0 / 0.0
local neginf = -1.0 / 0.0
local nan = 0.0 / 0.0
assertEquals(type(inf), 'number', 'inf has type number')
assertEquals(type(neginf), 'number', '-inf has type number')
assertEquals(type(nan), 'number', 'nan has type number')
assertEquals(math.type(inf), 'float', 'inf has math.type float')
assertEquals(math.type(neginf), 'float', '-inf has math.type float')
assertEquals(math.type(nan), 'float', 'nan has math.type float')
end
-- 23.16. Type consistency across operations
do
local i = 10
local sum_i = 0
for n = 1, 5 do
sum_i = sum_i + n
assertEquals(math.type(sum_i), 'integer', 'integer sum stays integer')
end
local f = 10.0
local sum_f = 0.0
for n = 1.0, 5.0 do
sum_f = sum_f + n
assertEquals(math.type(sum_f), 'float', 'float sum stays float')
end
end