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