Skip to main content

lua_stdlib/
math_lib.rs

1//! Standard mathematical library — `math.*`
2//!
3//! Translated from `src/lmathlib.c` (Lua 5.4.7, 782 lines, 28 functions).
4//!
5//! The PRNG is xoshiro256** operating on four 64-bit words. In C the
6//! implementation has two code paths (64-bit integers vs two 32-bit halves);
7//! Rust always has `u64`, so only the 64-bit path is kept.
8//!
9//! Deprecated compat functions guarded by `LUA_COMPAT_MATHLIB` (cosh, sinh,
10//! tanh, pow, frexp, ldexp, log10, atan2) are omitted; we target Lua 5.4
11//! semantics only. See PORTING.md §13.
12
13// PORT NOTE: All imports below will be unresolved until Phase B lands the
14// lua-types crate. Expected Phase-A errors: E0432, E0412, E0433, E0425.
15use lua_types::{LuaError, LuaType, LuaValue};
16use crate::state_stub::{LuaState, LuaStateStubExt as _, lua_CFunction as LuaCFn, upvalue_index, CompareOp, LuaDebug};
17
18// ── Constants ──────────────────────────────────────────────────────────────
19
20/// C: `#define PI (l_mathop(3.141592653589793238462643383279502884))`
21///
22/// Higher precision than `std::f64::consts::PI`; matches the C source literal.
23const PI: f64 = 3.141592653589793238462643383279502884_f64;
24
25/// Number of binary digits in the mantissa of `lua_Number` (f64).
26/// C: `#define FIGS l_floatatt(MANT_DIG)` — capped at 64.
27const FIGS: u32 = 53; // DBL_MANT_DIG for f64
28
29/// Bits to discard from the 64-bit random word before float conversion.
30/// C: `#define shift64_FIG (64 - FIGS)`
31const SHIFT64_FIG: u32 = 64 - FIGS; // = 11
32
33// ── Type aliases for library registration ─────────────────────────────────
34
35/// A Lua C-style function: takes the Lua state, returns count of pushed values.
36/// PORT NOTE: Phase B will unify with `lua_types::LuaCFunction`.
37type LuaCFunction = fn(&mut LuaState) -> Result<usize, LuaError>;
38
39/// An entry in the library registration table (name, optional function).
40/// `None` is used for placeholder entries whose values are set manually
41/// (e.g. `pi`, `huge`, `maxinteger`, `mininteger`, `random`, `randomseed`).
42/// PORT NOTE: Phase B will unify with `lua_types::LibReg`.
43struct LibReg {
44    name: &'static [u8],
45    func: Option<LuaCFunction>,
46}
47
48// ── PRNG state ────────────────────────────────────────────────────────────
49
50/// State for the xoshiro256** PRNG.
51/// C: `typedef struct { Rand64 s[4]; } RanState;`
52///
53/// In C this is stored as raw `lua_newuserdatauv` memory and accessed by
54/// casting the userdata pointer. Until typed-userdata closure upvalues land
55/// in Phase B, we keep the PRNG state in a thread-local cell so that
56/// `math.random` and `math.randomseed` are callable from Lua. This collapses
57/// per-lua_State PRNG isolation to per-thread, which is sufficient for the
58/// 5.4 test corpus.
59struct RanState {
60    s: [u64; 4],
61}
62
63thread_local! {
64    static RAN_STATE: std::cell::RefCell<RanState> =
65        std::cell::RefCell::new(RanState { s: [0xff, 0xff, 0xff, 0xff] });
66}
67
68// ── Pure PRNG algorithms ──────────────────────────────────────────────────
69
70/// Advance the xoshiro256** state by one step and return the next raw 64-bit
71/// pseudo-random value.
72///
73/// C: `static Rand64 nextrand(Rand64 *state)` (64-bit implementation).
74fn next_rand(s: &mut [u64; 4]) -> u64 {
75    // C: Rand64 state0 = state[0]; … state3 = state[3] ^ state1;
76    let s0 = s[0];
77    let s1 = s[1];
78    let s2 = s[2] ^ s0;
79    let s3 = s[3] ^ s1;
80    // C: Rand64 res = rotl(state1 * 5, 7) * 9;
81    let res = s1.wrapping_mul(5).rotate_left(7).wrapping_mul(9);
82    s[0] = s0 ^ s3;
83    s[1] = s1 ^ s2;
84    s[2] = s2 ^ (s1 << 17);
85    // C: state[3] = rotl(state3, 45);
86    s[3] = s3.rotate_left(45);
87    res
88}
89
90/// Convert a raw 64-bit PRNG output to a float in [0.0, 1.0).
91///
92/// C: `static lua_Number I2d(Rand64 x)` (64-bit implementation).
93/// Takes the top FIGS=53 bits, interprets them as a signed integer, scales
94/// by `scaleFIG = 0.5 / 2^52`, then corrects the two's-complement sign.
95fn rand_to_float(x: u64) -> f64 {
96    // C: SRand64 sx = (SRand64)(trim64(x) >> shift64_FIG);
97    let sx = (x >> SHIFT64_FIG) as i64;
98    // C: scaleFIG = l_mathop(0.5) / ((Rand64)1 << (FIGS - 1))
99    //            = 0.5 / 2^52
100    let scale_fig: f64 = 0.5 / ((1u64 << (FIGS - 1)) as f64);
101    // C: lua_Number res = (lua_Number)(sx) * scaleFIG;
102    let mut res = (sx as f64) * scale_fig;
103    // C: if (sx < 0) res += l_mathop(1.0);  /* correct two's complement */
104    if sx < 0 {
105        res += 1.0;
106    }
107    debug_assert!(0.0 <= res && res < 1.0);
108    res
109}
110
111/// Initialise the four PRNG words from two seed values.
112///
113/// C: `static void setseed(lua_State *L, Rand64 *state, lua_Unsigned n1, n2)`
114///
115/// PORT NOTE: The Lua pushes (n1, n2) are done at the call site in Rust so
116/// that this function does not need `&mut LuaState`, avoiding a borrow
117/// conflict with the upvalue `RanState`.
118fn set_seed_words(s: &mut [u64; 4], n1: u64, n2: u64) {
119    // C: state[0] = Int2I(n1); state[1] = Int2I(0xff); …
120    s[0] = n1;
121    s[1] = 0xff; // avoid a zero state
122    s[2] = n2;
123    s[3] = 0;
124    // C: for (i = 0; i < 16; i++) nextrand(state);
125    for _ in 0..16 {
126        next_rand(s); // discard initial values to "spread" seed
127    }
128}
129
130/// Project `ran` uniformly into [0, n].
131///
132/// C: `static lua_Unsigned project(lua_Unsigned ran, lua_Unsigned n, RanState *state)`
133///
134/// Uses rejection sampling with the smallest Mersenne number ≥ n as a mask.
135/// Takes `&mut [u64; 4]` rather than `&mut RanState` to avoid nested borrows
136/// at call sites.
137fn project(mut ran: u64, n: u64, s: &mut [u64; 4]) -> u64 {
138    // C: if ((n & (n + 1)) == 0) return ran & n;  /* n+1 is power of 2, no bias */
139    if (n & n.wrapping_add(1)) == 0 {
140        return ran & n;
141    }
142    // Compute the smallest (2^b - 1) not smaller than n.
143    let mut lim = n;
144    lim |= lim >> 1;
145    lim |= lim >> 2;
146    lim |= lim >> 4;
147    lim |= lim >> 8;
148    lim |= lim >> 16;
149    lim |= lim >> 32; // u64 always has 64 bits; C guards this with #if
150    debug_assert!((lim & lim.wrapping_add(1)) == 0); // lim+1 is a power of 2
151    debug_assert!(lim >= n);
152    debug_assert!((lim >> 1) < n);
153    // C: while ((ran &= lim) > n) ran = I2UInt(nextrand(state->s));
154    loop {
155        ran &= lim;
156        if ran <= n {
157            break;
158        }
159        ran = next_rand(s);
160    }
161    ran
162}
163
164// ── Helpers ───────────────────────────────────────────────────────────────
165
166/// Convert `d` to integer and push it; push the float unchanged if it doesn't
167/// fit exactly in an i64.
168///
169/// C: `static void pushnumint(lua_State *L, lua_Number d)`
170fn push_num_int(state: &mut LuaState, d: f64) {
171    // C: if (lua_numbertointeger(d, &n)) lua_pushinteger(L, n);
172    //    else lua_pushnumber(L, d);
173    //
174    // lua_numbertointeger: d >= LUA_MININTEGER as float &&
175    //                      d <  -(LUA_MININTEGER as float)
176    let min_f = i64::MIN as f64; // -2^63
177    let max_plus1_f = -(i64::MIN as f64); // 2^63 (one past i64::MAX as float)
178    if d >= min_f && d < max_plus1_f {
179        state.push(LuaValue::Int(d as i64));
180    } else {
181        state.push(LuaValue::Float(d));
182    }
183}
184
185// ── Basic math functions ──────────────────────────────────────────────────
186
187/// `math.abs(x)` — absolute value, preserving integer type when possible.
188///
189/// C: `static int math_abs(lua_State *L)`
190fn math_abs(state: &mut LuaState) -> Result<usize, LuaError> {
191    // C: if (lua_isinteger(L, 1))
192    if matches!(state.value_at(1), LuaValue::Int(_)) {
193        let n = state.to_integer(1).unwrap_or(0);
194        let n = if n < 0 {
195            (0u64.wrapping_sub(n as u64)) as i64
196        } else {
197            n
198        };
199        state.push(LuaValue::Int(n));
200    } else {
201        // C: lua_pushnumber(L, fabs(luaL_checknumber(L, 1)));
202        let x = state.check_number(1)?;
203        state.push(LuaValue::Float(x.abs()));
204    }
205    Ok(1)
206}
207
208/// `math.sin(x)` — sine (radians).
209///
210/// C: `static int math_sin(lua_State *L)`
211fn math_sin(state: &mut LuaState) -> Result<usize, LuaError> {
212    // C: lua_pushnumber(L, sin(luaL_checknumber(L, 1)));
213    let x = state.check_number(1)?;
214    state.push(LuaValue::Float(x.sin()));
215    Ok(1)
216}
217
218/// `math.cos(x)` — cosine (radians).
219///
220/// C: `static int math_cos(lua_State *L)`
221fn math_cos(state: &mut LuaState) -> Result<usize, LuaError> {
222    let x = state.check_number(1)?;
223    state.push(LuaValue::Float(x.cos()));
224    Ok(1)
225}
226
227/// `math.tan(x)` — tangent (radians).
228///
229/// C: `static int math_tan(lua_State *L)`
230fn math_tan(state: &mut LuaState) -> Result<usize, LuaError> {
231    let x = state.check_number(1)?;
232    state.push(LuaValue::Float(x.tan()));
233    Ok(1)
234}
235
236/// `math.asin(x)` — arc-sine, result in radians.
237///
238/// C: `static int math_asin(lua_State *L)`
239fn math_asin(state: &mut LuaState) -> Result<usize, LuaError> {
240    let x = state.check_number(1)?;
241    state.push(LuaValue::Float(x.asin()));
242    Ok(1)
243}
244
245/// `math.acos(x)` — arc-cosine, result in radians.
246///
247/// C: `static int math_acos(lua_State *L)`
248fn math_acos(state: &mut LuaState) -> Result<usize, LuaError> {
249    let x = state.check_number(1)?;
250    state.push(LuaValue::Float(x.acos()));
251    Ok(1)
252}
253
254/// `math.atan(y [, x])` — arc-tangent of y/x (defaults x=1), result in
255/// radians. Subsumes C's `atan2` when x is provided.
256///
257/// C: `static int math_atan(lua_State *L)`
258fn math_atan(state: &mut LuaState) -> Result<usize, LuaError> {
259    // C: lua_Number y = luaL_checknumber(L, 1);
260    let y = state.check_number(1)?;
261    // C: lua_Number x = luaL_optnumber(L, 2, 1);
262    let x = state.opt_number(2, 1.0)?;
263    // C: lua_pushnumber(L, atan2(y, x));
264    state.push(LuaValue::Float(y.atan2(x)));
265    Ok(1)
266}
267
268/// `math.tointeger(x)` — convert x to an integer or return false.
269///
270/// C: `static int math_toint(lua_State *L)`
271fn math_toint(state: &mut LuaState) -> Result<usize, LuaError> {
272    // C: int valid; lua_Integer n = lua_tointegerx(L, 1, &valid);
273    // TODO(port): state.to_integer_opt(1) should return Option<i64>;
274    // the method name/signature will be confirmed in Phase B.
275    let maybe_n: Option<i64> = state.to_integer_opt(1);
276    if let Some(n) = maybe_n {
277        // C: if (l_likely(valid)) lua_pushinteger(L, n);
278        state.push(LuaValue::Int(n));
279    } else {
280        // C: luaL_checkany(L, 1); luaL_pushfail(L);
281        state.check_any(1)?;
282        // PORT NOTE: luaL_pushfail in Lua 5.4 pushes false (not nil).
283        state.push(LuaValue::Bool(false));
284    }
285    Ok(1)
286}
287
288/// `math.floor(x)` — largest integer ≤ x.
289///
290/// C: `static int math_floor(lua_State *L)`
291fn math_floor(state: &mut LuaState) -> Result<usize, LuaError> {
292    // C: if (lua_isinteger(L, 1)) lua_settop(L, 1);  /* integer is its own floor */
293    if matches!(state.value_at(1), LuaValue::Int(_)) {
294        // Must go through the public C-API set_top (relative to the call
295        // frame); the inherent LuaState::set_top treats its argument as an
296        // absolute StackIdx.
297        lua_vm::api::set_top(state, 1)?;
298    } else {
299        // C: lua_Number d = floor(luaL_checknumber(L, 1)); pushnumint(L, d);
300        let d = state.check_number(1)?.floor();
301        push_num_int(state, d);
302    }
303    Ok(1)
304}
305
306/// `math.ceil(x)` — smallest integer ≥ x.
307///
308/// C: `static int math_ceil(lua_State *L)`
309fn math_ceil(state: &mut LuaState) -> Result<usize, LuaError> {
310    // C: if (lua_isinteger(L, 1)) lua_settop(L, 1);  /* integer is its own ceil */
311    if matches!(state.value_at(1), LuaValue::Int(_)) {
312        // Public C-API set_top (relative); inherent LuaState::set_top is absolute.
313        lua_vm::api::set_top(state, 1)?;
314    } else {
315        let d = state.check_number(1)?.ceil();
316        push_num_int(state, d);
317    }
318    Ok(1)
319}
320
321/// `math.fmod(x, y)` — floating-point remainder (same sign as x).
322///
323/// C: `static int math_fmod(lua_State *L)`
324fn math_fmod(state: &mut LuaState) -> Result<usize, LuaError> {
325    // C: if (lua_isinteger(L, 1) && lua_isinteger(L, 2))
326    if matches!(state.value_at(1), LuaValue::Int(_))
327        && matches!(state.value_at(2), LuaValue::Int(_))
328    {
329        let a = state.to_integer(1).unwrap_or(0);
330        let d = state.to_integer(2).unwrap_or(0);
331        if (d as u64).wrapping_add(1) <= 1 {
332            if d == 0 {
333                return Err(LuaError::arg_error(2, "zero"));
334            }
335            state.push(LuaValue::Int(0));
336        } else {
337            state.push(LuaValue::Int(a % d));
338        }
339    } else {
340        // C: lua_pushnumber(L, fmod(luaL_checknumber(L, 1), luaL_checknumber(L, 2)));
341        let x = state.check_number(1)?;
342        let y = state.check_number(2)?;
343        state.push(LuaValue::Float(x % y));
344    }
345    Ok(1)
346}
347
348/// `math.modf(x)` — split into integer and fractional parts; returns 2 values.
349///
350/// C: `static int math_modf(lua_State *L)`
351///
352/// PORT NOTE: Does not use `modf` (avoids `double *` / `float *` ABI mismatch
353/// for non-double `lua_Number`). Instead, uses ceil/floor + subtraction.
354fn math_modf(state: &mut LuaState) -> Result<usize, LuaError> {
355    // C: if (lua_isinteger(L, 1)) { lua_settop(L, 1); lua_pushnumber(L, 0); }
356    if matches!(state.value_at(1), LuaValue::Int(_)) {
357        // Public C-API set_top (relative); inherent LuaState::set_top is absolute.
358        lua_vm::api::set_top(state, 1)?; // integer part is the integer itself
359        state.push(LuaValue::Float(0.0)); // no fractional part
360    } else {
361        let n = state.check_number(1)?;
362        // C: lua_Number ip = (n < 0) ? ceil(n) : floor(n);
363        let ip = if n < 0.0 { n.ceil() } else { n.floor() };
364        // C: pushnumint(L, ip);
365        push_num_int(state, ip);
366        // C: lua_pushnumber(L, (n == ip) ? 0.0 : (n - ip));
367        let frac = if n == ip { 0.0 } else { n - ip };
368        state.push(LuaValue::Float(frac));
369    }
370    Ok(2)
371}
372
373/// `math.sqrt(x)` — square root.
374///
375/// C: `static int math_sqrt(lua_State *L)`
376fn math_sqrt(state: &mut LuaState) -> Result<usize, LuaError> {
377    let x = state.check_number(1)?;
378    state.push(LuaValue::Float(x.sqrt()));
379    Ok(1)
380}
381
382/// `math.ult(m, n)` — unsigned less-than on integers.
383///
384/// C: `static int math_ult(lua_State *L)`
385fn math_ult(state: &mut LuaState) -> Result<usize, LuaError> {
386    // C: lua_Integer a = luaL_checkinteger(L, 1); lua_Integer b = …(L, 2);
387    let a = state.check_integer(1)?;
388    let b = state.check_integer(2)?;
389    // C: lua_pushboolean(L, (lua_Unsigned)a < (lua_Unsigned)b);
390    state.push(LuaValue::Bool((a as u64) < (b as u64)));
391    Ok(1)
392}
393
394/// `math.log(x [, base])` — logarithm; natural if base omitted.
395///
396/// C: `static int math_log(lua_State *L)`
397fn math_log(state: &mut LuaState) -> Result<usize, LuaError> {
398    let x = state.check_number(1)?;
399    // C: if (lua_isnoneornil(L, 2)) res = log(x);
400    let res = if matches!(state.type_at(2), LuaType::None | LuaType::Nil) {
401        x.ln()
402    } else {
403        let base = state.check_number(2)?;
404        // C: if (base == 2.0) res = log2(x);  (guarded by !LUA_USE_C89)
405        if base == 2.0 {
406            x.log2()
407        } else if base == 10.0 {
408            // C: else if (base == 10.0) res = log10(x);
409            x.log10()
410        } else {
411            // C: else res = log(x) / log(base);
412            x.ln() / base.ln()
413        }
414    };
415    state.push(LuaValue::Float(res));
416    Ok(1)
417}
418
419/// `math.exp(x)` — e raised to the power x.
420///
421/// C: `static int math_exp(lua_State *L)`
422fn math_exp(state: &mut LuaState) -> Result<usize, LuaError> {
423    let x = state.check_number(1)?;
424    state.push(LuaValue::Float(x.exp()));
425    Ok(1)
426}
427
428/// `math.deg(x)` — convert radians to degrees.
429///
430/// C: `static int math_deg(lua_State *L)`
431fn math_deg(state: &mut LuaState) -> Result<usize, LuaError> {
432    // C: lua_pushnumber(L, luaL_checknumber(L, 1) * (180.0 / PI));
433    let x = state.check_number(1)?;
434    state.push(LuaValue::Float(x * (180.0 / PI)));
435    Ok(1)
436}
437
438/// `math.rad(x)` — convert degrees to radians.
439///
440/// C: `static int math_rad(lua_State *L)`
441fn math_rad(state: &mut LuaState) -> Result<usize, LuaError> {
442    // C: lua_pushnumber(L, luaL_checknumber(L, 1) * (PI / 180.0));
443    let x = state.check_number(1)?;
444    state.push(LuaValue::Float(x * (PI / 180.0)));
445    Ok(1)
446}
447
448/// `math.min(x, ...)` — minimum of all arguments (uses Lua `<` comparison).
449///
450/// C: `static int math_min(lua_State *L)`
451fn math_min(state: &mut LuaState) -> Result<usize, LuaError> {
452    // C: int n = lua_gettop(L); int imin = 1;
453    let n = state.get_top();
454    let mut imin: i32 = 1;
455    // C: luaL_argcheck(L, n >= 1, 1, "value expected");
456    if n < 1 {
457        return Err(LuaError::arg_error(1, "value expected"));
458    }
459    for i in 2..=n {
460        if state.compare_lt(i, imin)? {
461            imin = i;
462        }
463    }
464    // C: lua_pushvalue(L, imin);
465    state.push_value(imin)?;
466    Ok(1)
467}
468
469/// `math.max(x, ...)` — maximum of all arguments (uses Lua `<` comparison).
470///
471/// C: `static int math_max(lua_State *L)`
472fn math_max(state: &mut LuaState) -> Result<usize, LuaError> {
473    // C: int n = lua_gettop(L); int imax = 1;
474    let n = state.get_top();
475    let mut imax: i32 = 1;
476    // C: luaL_argcheck(L, n >= 1, 1, "value expected");
477    if n < 1 {
478        return Err(LuaError::arg_error(1, "value expected"));
479    }
480    // C: for (i = 2; i <= n; i++) { if (lua_compare(L, imax, i, LUA_OPLT)) imax = i; }
481    for i in 2..=n {
482        if state.compare_lt(imax, i)? {
483            imax = i;
484        }
485    }
486    // C: lua_pushvalue(L, imax);
487    state.push_value(imax)?;
488    Ok(1)
489}
490
491/// `math.type(x)` — return `"integer"`, `"float"`, or false for non-numbers.
492///
493/// C: `static int math_type(lua_State *L)`
494fn math_type(state: &mut LuaState) -> Result<usize, LuaError> {
495    // C: if (lua_type(L, 1) == LUA_TNUMBER)
496    if matches!(state.type_at(1), LuaType::Number) {
497        // C: lua_pushstring(L, lua_isinteger(L, 1) ? "integer" : "float");
498        if matches!(state.value_at(1), LuaValue::Int(_)) {
499            state.push_string(b"integer");
500        } else {
501            state.push_string(b"float");
502        }
503    } else {
504        // C: luaL_checkany(L, 1); luaL_pushfail(L);
505        state.check_any(1)?;
506        // PORT NOTE: luaL_pushfail pushes false in Lua 5.4.4+.
507        state.push(LuaValue::Bool(false));
508    }
509    Ok(1)
510}
511
512// ── PRNG-backed Lua functions ─────────────────────────────────────────────
513
514/// `math.random([m [, n]])` — pseudo-random number generation.
515///
516/// C: `static int math_random(lua_State *L)`
517///
518/// With no arguments: float in [0, 1).
519/// With one argument n: integer in [1, n] (or full random u64 if n == 0).
520/// With two arguments m, n: integer in [m, n].
521fn math_random(state: &mut LuaState) -> Result<usize, LuaError> {
522    // C: RanState *state = (RanState *)lua_touserdata(L, lua_upvalueindex(1));
523    // TODO(port): RanState is stored as typed userdata in closure upvalue 1.
524    // Phase B must implement `state.upvalue_userdata_mut::<RanState>(1)` using
525    // interior mutability (e.g. GcRef<RefCell<RanState>>) to avoid the borrow
526    // conflict between &mut RanState and subsequent &mut LuaState push calls.
527    //
528    // For Phase A: advance PRNG and get args via separate borrows.
529    let rv = advance_prng(state)?;
530    let n_args = state.get_top();
531
532    if n_args == 0 {
533        // C: case 0: lua_pushnumber(L, I2d(rv)); return 1;
534        state.push(LuaValue::Float(rand_to_float(rv)));
535        return Ok(1);
536    }
537
538    let (low, up) = match n_args {
539        1 => {
540            // C: case 1: low = 1; up = luaL_checkinteger(L, 1);
541            let up = state.check_integer(1)?;
542            if up == 0 {
543                // C: if (up == 0) { lua_pushinteger(L, I2UInt(rv)); return 1; }
544                // I2UInt(rv) = rv (trivial for u64)
545                state.push(LuaValue::Int(rv as i64));
546                return Ok(1);
547            }
548            (1i64, up)
549        }
550        2 => {
551            // C: case 2: low = luaL_checkinteger(L, 1); up = luaL_checkinteger(L, 2);
552            let low = state.check_integer(1)?;
553            let up = state.check_integer(2)?;
554            (low, up)
555        }
556        _ => {
557            // C: default: return luaL_error(L, "wrong number of arguments");
558            return Err(LuaError::runtime(format_args!(
559                "wrong number of arguments"
560            )));
561        }
562    };
563
564    // C: luaL_argcheck(L, low <= up, 1, "interval is empty");
565    if low > up {
566        return Err(LuaError::arg_error(1, "interval is empty"));
567    }
568
569    // C: p = project(I2UInt(rv), (lua_Unsigned)up - (lua_Unsigned)low, state);
570    // C: lua_pushinteger(L, p + (lua_Unsigned)low);
571    let range = (up as u64).wrapping_sub(low as u64);
572    let p = project_from_upvalue(state, rv, range)?;
573    state.push(LuaValue::Int((p as u64).wrapping_add(low as u64) as i64));
574    Ok(1)
575}
576
577/// `math.randomseed([x [, y]])` — seed the PRNG; returns two seed values.
578///
579/// C: `static int math_randomseed(lua_State *L)`
580fn math_randomseed(state: &mut LuaState) -> Result<usize, LuaError> {
581    // C: RanState *state = (RanState *)lua_touserdata(L, lua_upvalueindex(1));
582    // TODO(port): same upvalue userdata access issue as math_random.
583    if matches!(state.type_at(1), LuaType::None) {
584        // C: if (lua_isnone(L, 1)) { randseed(L, state); }
585        // randseed uses time(NULL) and address of L for entropy.
586        apply_random_seed(state)?;
587    } else {
588        // C: lua_Integer n1 = luaL_checkinteger(L, 1);
589        //    lua_Integer n2 = luaL_optinteger(L, 2, 0);
590        let n1 = state.check_integer(1)? as u64;
591        let n2 = state.opt_integer(2, 0)? as u64;
592        // C: setseed(L, state->s, n1, n2);
593        apply_set_seed(state, n1, n2)?;
594    }
595    Ok(2) // C: return 2;  /* return seeds */
596}
597
598/// Advance the PRNG stored in the thread-local `RAN_STATE` and return the
599/// raw 64-bit output.
600///
601/// PORT NOTE: In C this draws from the userdata in closure upvalue 1. The
602/// Rust port stores the PRNG state in a thread-local until typed-userdata
603/// closure upvalues are wired up. Storage location is the only difference;
604/// the algorithm is unchanged.
605fn advance_prng(_state: &mut LuaState) -> Result<u64, LuaError> {
606    Ok(RAN_STATE.with(|r| next_rand(&mut r.borrow_mut().s)))
607}
608
609/// Apply rejection sampling for `math.random` using the thread-local PRNG.
610///
611/// PORT NOTE: see `advance_prng` for the thread-local rationale.
612fn project_from_upvalue(
613    _state: &mut LuaState,
614    ran: u64,
615    n: u64,
616) -> Result<u64, LuaError> {
617    Ok(RAN_STATE.with(|r| project(ran, n, &mut r.borrow_mut().s)))
618}
619
620/// Seed the PRNG from wall-clock time (entropy source).
621///
622/// C: `randseed(L, state)` — uses `time(NULL)` and address of `L` as seeds.
623///
624/// TODO(port): must write n1 and n2 back to the upvalue RanState.
625fn apply_random_seed(state: &mut LuaState) -> Result<(), LuaError> {
626    // C: lua_Unsigned seed1 = (lua_Unsigned)time(NULL);
627    // PORT NOTE: std::time is not in the banned list (only std::fs/net/process).
628    let seed1 = std::time::SystemTime::now()
629        .duration_since(std::time::UNIX_EPOCH)
630        .map(|d| d.as_secs())
631        .unwrap_or(0);
632    // C: lua_Unsigned seed2 = (lua_Unsigned)(size_t)L;
633    // TODO(port): C uses address of L for ASLR entropy; no safe equivalent.
634    // Phase B can use a thread-local counter or OS entropy instead.
635    let seed2: u64 = 0;
636    apply_set_seed(state, seed1, seed2)
637}
638
639/// Apply explicit seeds to the PRNG and push them onto the stack.
640///
641/// C: `setseed(L, state->s, n1, n2)` — also pushes n1, n2.
642///
643/// PORT NOTE: writes seeds into the thread-local RanState (see `advance_prng`).
644fn apply_set_seed(state: &mut LuaState, n1: u64, n2: u64) -> Result<(), LuaError> {
645    RAN_STATE.with(|r| set_seed_words(&mut r.borrow_mut().s, n1, n2));
646    // C: lua_pushinteger(L, n1); lua_pushinteger(L, n2);
647    state.push(LuaValue::Int(n1 as i64));
648    state.push(LuaValue::Int(n2 as i64));
649    Ok(())
650}
651
652/// Register `math.random` and `math.randomseed` on the math library table at
653/// stack top, after seeding the thread-local PRNG.
654///
655/// C: `static void setrandfunc(lua_State *L)`
656///
657/// PORT NOTE: C stores the PRNG inside a userdata bound as upvalue 1 of both
658/// closures. Until typed userdata closure upvalues are available, the Rust
659/// port keeps the PRNG in a thread-local (see `RAN_STATE`) and registers the
660/// functions as plain non-closure entries on the library table.
661fn set_rand_func(state: &mut LuaState) -> Result<(), LuaError> {
662    apply_random_seed(state)?;
663    state.pop_n(2);
664
665    state.push_c_function(math_random)?;
666    state.set_field(-2, b"random")?;
667    state.push_c_function(math_randomseed)?;
668    state.set_field(-2, b"randomseed")?;
669    Ok(())
670}
671
672// ── Library registration table ────────────────────────────────────────────
673
674/// The `math` library function table.
675///
676/// C: `static const luaL_Reg mathlib[]`
677///
678/// Placeholder entries (`None`) are filled in manually by `luaopen_math`
679/// (`pi`, `huge`, `maxinteger`, `mininteger`) or by `set_rand_func`
680/// (`random`, `randomseed`).
681static MATHLIB: &[LibReg] = &[
682    LibReg { name: b"abs",        func: Some(math_abs)    },
683    LibReg { name: b"acos",       func: Some(math_acos)   },
684    LibReg { name: b"asin",       func: Some(math_asin)   },
685    LibReg { name: b"atan",       func: Some(math_atan)   },
686    LibReg { name: b"ceil",       func: Some(math_ceil)   },
687    LibReg { name: b"cos",        func: Some(math_cos)    },
688    LibReg { name: b"deg",        func: Some(math_deg)    },
689    LibReg { name: b"exp",        func: Some(math_exp)    },
690    LibReg { name: b"tointeger",  func: Some(math_toint)  },
691    LibReg { name: b"floor",      func: Some(math_floor)  },
692    LibReg { name: b"fmod",       func: Some(math_fmod)   },
693    LibReg { name: b"ult",        func: Some(math_ult)    },
694    LibReg { name: b"log",        func: Some(math_log)    },
695    LibReg { name: b"max",        func: Some(math_max)    },
696    LibReg { name: b"min",        func: Some(math_min)    },
697    LibReg { name: b"modf",       func: Some(math_modf)   },
698    LibReg { name: b"rad",        func: Some(math_rad)    },
699    LibReg { name: b"sin",        func: Some(math_sin)    },
700    LibReg { name: b"sqrt",       func: Some(math_sqrt)   },
701    LibReg { name: b"tan",        func: Some(math_tan)    },
702    LibReg { name: b"type",       func: Some(math_type)   },
703    // Placeholders; values are set manually in luaopen_math / set_rand_func.
704    LibReg { name: b"random",     func: None },
705    LibReg { name: b"randomseed", func: None },
706    LibReg { name: b"pi",         func: None },
707    LibReg { name: b"huge",       func: None },
708    LibReg { name: b"maxinteger", func: None },
709    LibReg { name: b"mininteger", func: None },
710];
711
712static MATHLIB_FUNCS: &[(&[u8], LuaCFunction)] = &[
713    (b"abs",        math_abs),
714    (b"acos",       math_acos),
715    (b"asin",       math_asin),
716    (b"atan",       math_atan),
717    (b"ceil",       math_ceil),
718    (b"cos",        math_cos),
719    (b"deg",        math_deg),
720    (b"exp",        math_exp),
721    (b"tointeger",  math_toint),
722    (b"floor",      math_floor),
723    (b"fmod",       math_fmod),
724    (b"ult",        math_ult),
725    (b"log",        math_log),
726    (b"max",        math_max),
727    (b"min",        math_min),
728    (b"modf",       math_modf),
729    (b"rad",        math_rad),
730    (b"sin",        math_sin),
731    (b"sqrt",       math_sqrt),
732    (b"tan",        math_tan),
733    (b"type",       math_type),
734];
735
736// ── Module entry point ────────────────────────────────────────────────────
737
738/// Open the `math` library: create the table, populate constants, register
739/// the PRNG functions with their shared `RanState` upvalue.
740///
741/// C: `LUAMOD_API int luaopen_math(lua_State *L)`
742///
743/// `LUAMOD_API` → `pub` (see macros.tsv).
744pub fn luaopen_math(state: &mut LuaState) -> Result<usize, LuaError> {
745    // C: luaL_newlib(L, mathlib);
746    // Creates a new table and registers all non-None entries from MATHLIB.
747    state.new_lib(MATHLIB_FUNCS)?;
748
749    // C: lua_pushnumber(L, PI); lua_setfield(L, -2, "pi");
750    state.push(LuaValue::Float(PI));
751    state.set_field(-2, b"pi")?;
752
753    // C: lua_pushnumber(L, (lua_Number)HUGE_VAL); lua_setfield(L, -2, "huge");
754    state.push(LuaValue::Float(f64::INFINITY));
755    state.set_field(-2, b"huge")?;
756
757    // C: lua_pushinteger(L, LUA_MAXINTEGER); lua_setfield(L, -2, "maxinteger");
758    // LUA_MAXINTEGER = i64::MAX (lua_Integer is int64_t in default config).
759    state.push(LuaValue::Int(i64::MAX));
760    state.set_field(-2, b"maxinteger")?;
761
762    // C: lua_pushinteger(L, LUA_MININTEGER); lua_setfield(L, -2, "mininteger");
763    state.push(LuaValue::Int(i64::MIN));
764    state.set_field(-2, b"mininteger")?;
765
766    // C: setrandfunc(L);
767    // Registers math.random and math.randomseed as upvalue-bearing closures.
768    set_rand_func(state)?;
769
770    Ok(1)
771}
772
773// ──────────────────────────────────────────────────────────────────────────
774// PORT STATUS
775//   source:        src/lmathlib.c  (782 lines, 28 functions)
776//   target_crate:  lua-stdlib
777//   confidence:    medium
778//   todos:         16
779//   port_notes:    8
780//   unsafe_blocks: 0
781//   notes:         All basic math functions are mechanically faithful. The
782//                  PRNG xoshiro256** algorithm is correctly translated using
783//                  native u64 (only the 64-bit code path; the 32-bit fallback
784//                  is dropped). The main Phase-B work is wiring up the upvalue
785//                  RanState userdata: advance_prng, project_from_upvalue,
786//                  apply_random_seed, apply_set_seed, and set_rand_func all
787//                  carry TODO(port) stubs where typed userdata + interior
788//                  mutability (RefCell) is required to avoid borrow conflicts.
789//                  Deprecated LUA_COMPAT_MATHLIB functions are omitted per
790//                  PORTING.md §13. state.new_lib, state.set_field,
791//                  state.compare_lt, state.push_value, state.opt_number,
792//                  state.opt_integer, state.check_integer, state.check_number,
793//                  state.check_any, state.to_integer_opt, state.get_top,
794//                  state.set_top, state.pop_n API names assumed; Phase B
795//                  will reconcile with the actual LuaState impl.
796// ──────────────────────────────────────────────────────────────────────────