lua_stdlib/math_lib.rs
1//! Standard mathematical library — `math.*`.
2//!
3//! The PRNG is xoshiro256** operating on four 64-bit words (the single 64-bit
4//! code path; there is no 32-bit fallback to keep).
5//!
6//! The deprecated `LUA_COMPAT_MATHLIB` roster (`cosh`, `sinh`, `tanh`, `pow`,
7//! `log10`, `atan2`) ships in the default 5.1/5.2/5.3/5.4 builds and is dropped
8//! in 5.5; `frexp`/`ldexp` survive into 5.5. `atan2` is an alias of `math_atan`.
9//! Which functions exist per version is the registration logic in
10//! [`luaopen_math`].
11
12use crate::state_stub::{LuaState, LuaStateStubExt as _};
13use lua_types::{LuaError, LuaType, LuaValue};
14
15// ── Constants ──────────────────────────────────────────────────────────────
16
17/// `math.pi`. Higher precision than `std::f64::consts::PI` (which is rounded to
18/// the nearest `f64`); both round-trip to the same `f64` bit pattern.
19const PI: f64 = 3.141592653589793238462643383279502884_f64;
20
21// ── Type aliases for library registration ─────────────────────────────────
22
23/// A Lua C-style function: takes the Lua state, returns count of pushed values.
24type LuaCFunction = fn(&mut LuaState) -> Result<usize, LuaError>;
25
26// ── PRNG state ────────────────────────────────────────────────────────────
27
28/// State for the xoshiro256** PRNG.
29///
30/// In C this is stored as raw `lua_newuserdatauv` memory and accessed by
31/// casting the userdata pointer. Until typed-userdata closure upvalues are
32/// available, the PRNG state instead lives in a thread-local cell so that
33/// `math.random` and `math.randomseed` are callable from Lua. This collapses
34/// per-`LuaState` PRNG isolation to per-thread, which is sufficient for the
35/// 5.4 test corpus.
36struct RanState {
37 s: [u64; 4],
38}
39
40thread_local! {
41 static RAN_STATE: std::cell::RefCell<RanState> =
42 std::cell::RefCell::new(RanState { s: [0xff, 0xff, 0xff, 0xff] });
43}
44
45/// The xoshiro256** generator: the bit-exact, load-bearing PRNG core.
46///
47/// Every function here is pinned byte-for-byte by the behavioral net (the
48/// `multiversion_oracle` PRNG-sequence tests on 5.4/5.5). The internals must NOT
49/// be reordered or "simplified" — any change to the arithmetic diverges from the
50/// reference stream. Grouping them in one private module makes that contract
51/// explicit; the callers reach them as `xoshiro::*`.
52mod xoshiro {
53 /// Number of binary digits in the `f64` mantissa (`DBL_MANT_DIG`).
54 const FIGS: u32 = 53;
55
56 /// Bits to discard from a 64-bit random word before float conversion (`= 11`):
57 /// the word is reduced to its top [`FIGS`] significant bits.
58 const SHIFT64_FIG: u32 = 64 - FIGS;
59
60 /// Advance the xoshiro256** state by one step and return the next raw 64-bit
61 /// pseudo-random value.
62 pub(super) fn next_rand(s: &mut [u64; 4]) -> u64 {
63 let s0 = s[0];
64 let s1 = s[1];
65 let s2 = s[2] ^ s0;
66 let s3 = s[3] ^ s1;
67 let res = s1.wrapping_mul(5).rotate_left(7).wrapping_mul(9);
68 s[0] = s0 ^ s3;
69 s[1] = s1 ^ s2;
70 s[2] = s2 ^ (s1 << 17);
71 s[3] = s3.rotate_left(45);
72 res
73 }
74
75 /// Convert a raw 64-bit PRNG output to a float in [0.0, 1.0).
76 ///
77 /// Takes the top FIGS=53 bits, interprets them as a signed integer, scales
78 /// by `scaleFIG = 0.5 / 2^52`, then corrects the two's-complement sign.
79 pub(super) fn rand_to_float(x: u64) -> f64 {
80 let sx = (x >> SHIFT64_FIG) as i64;
81 let scale_fig: f64 = 0.5 / ((1u64 << (FIGS - 1)) as f64);
82 let mut res = (sx as f64) * scale_fig;
83 if sx < 0 {
84 res += 1.0;
85 }
86 debug_assert!(0.0 <= res && res < 1.0);
87 res
88 }
89
90 /// Initialise the four PRNG words from two seed values.
91 ///
92 /// `s[1]` is forced to `0xff` so the state is never all-zero (xoshiro's
93 /// fixed point), and sixteen draws are discarded to spread the seed bits before
94 /// the first observable output. Takes the word array (not the [`LuaState`]) so
95 /// the caller can push the seed values without a borrow conflict against the
96 /// `RanState` upvalue.
97 pub(super) fn set_seed_words(s: &mut [u64; 4], n1: u64, n2: u64) {
98 s[0] = n1;
99 s[1] = 0xff;
100 s[2] = n2;
101 s[3] = 0;
102 for _ in 0..16 {
103 next_rand(s);
104 }
105 }
106
107 /// Project `ran` uniformly into `[0, n]` by rejection sampling.
108 ///
109 /// When `n + 1` is a power of two the low bits are already uniform, so a single
110 /// mask suffices. Otherwise `lim` is built as the smallest `2^b - 1` not smaller
111 /// than `n` (the bit-smear), and draws outside `[0, n]` are rejected and
112 /// redrawn. The `>> 32` smear step is unconditional here because `u64` always
113 /// has 64 bits; the C source guards it behind an `#if` on the integer width.
114 /// Takes the word array (not `&mut RanState`) to avoid nested borrows at the
115 /// call sites.
116 pub(super) fn project(mut ran: u64, n: u64, s: &mut [u64; 4]) -> u64 {
117 if (n & n.wrapping_add(1)) == 0 {
118 return ran & n;
119 }
120 let mut lim = n;
121 lim |= lim >> 1;
122 lim |= lim >> 2;
123 lim |= lim >> 4;
124 lim |= lim >> 8;
125 lim |= lim >> 16;
126 lim |= lim >> 32;
127 debug_assert!((lim & lim.wrapping_add(1)) == 0);
128 debug_assert!(lim >= n);
129 debug_assert!((lim >> 1) < n);
130 loop {
131 ran &= lim;
132 if ran <= n {
133 break;
134 }
135 ran = next_rand(s);
136 }
137 ran
138 }
139}
140
141// ── Helpers ───────────────────────────────────────────────────────────────
142
143/// Whether the argument at stack index `n` carries the integer subtype.
144///
145/// Several `math.*` functions (`abs`, `floor`, `ceil`, `fmod`, `modf`, `type`)
146/// branch on whether an argument is an `Int` versus a `Float`, preserving the
147/// integer subtype on the integer path. This names that recurring predicate so
148/// the branch reads as intent rather than as a raw `matches!` on the stack slot.
149fn arg_is_int(state: &mut LuaState, n: i32) -> bool {
150 matches!(state.value_at(n), LuaValue::Int(_))
151}
152
153/// The smallest `f64` whose value equals an `i64` exactly: `i64::MIN` as a
154/// float (`-2^63`, exactly representable).
155const I64_MIN_AS_F64: f64 = i64::MIN as f64;
156
157/// One past the largest representable `i64`, as a float: `2^63`. `i64::MAX`
158/// (`2^63 - 1`) is not exactly representable in `f64`, so the in-range test
159/// uses a strict `<` against this boundary rather than `<= i64::MAX as f64`.
160const I64_MAX_PLUS_1_AS_F64: f64 = -(i64::MIN as f64);
161
162/// Push `d` as an `Int` when it has an integer value that fits exactly in an
163/// `i64`, otherwise push it as a `Float`.
164///
165/// `floor`/`ceil`/`modf` use this to keep their result an integer subtype when
166/// it round-trips through `i64` without loss, falling back to a float for
167/// magnitudes outside the `i64` range. The bound check is half-open
168/// (`I64_MIN_AS_F64 <= d < I64_MAX_PLUS_1_AS_F64`) because `i64::MAX` itself is
169/// not exactly representable as an `f64`.
170fn push_int_or_float(state: &mut LuaState, d: f64) {
171 if d >= I64_MIN_AS_F64 && d < I64_MAX_PLUS_1_AS_F64 {
172 state.push(LuaValue::Int(d as i64));
173 } else {
174 state.push(LuaValue::Float(d));
175 }
176}
177
178/// Leave the first argument as the sole result (an integer floor/ceil/modf is
179/// already its own integer part — return it unchanged, preserving subtype).
180///
181/// This goes through the public, frame-relative [`lua_vm::api::set_top`] so the
182/// truncation is relative to the call frame; the inherent `LuaState::set_top`
183/// would treat its argument as an absolute `StackIdx` and discard the argument.
184fn keep_first_arg(state: &mut LuaState) -> Result<(), LuaError> {
185 lua_vm::api::set_top(state, 1)
186}
187
188// ── Basic math functions ──────────────────────────────────────────────────
189
190/// `math.abs(x)` — absolute value, preserving integer type when possible.
191///
192fn math_abs(state: &mut LuaState) -> Result<usize, LuaError> {
193 if arg_is_int(state, 1) {
194 let n = state.to_integer(1).unwrap_or(0);
195 let n = if n < 0 {
196 (0u64.wrapping_sub(n as u64)) as i64
197 } else {
198 n
199 };
200 state.push(LuaValue::Int(n));
201 } else {
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///
210fn math_sin(state: &mut LuaState) -> Result<usize, LuaError> {
211 let x = state.check_number(1)?;
212 state.push(LuaValue::Float(x.sin()));
213 Ok(1)
214}
215
216/// `math.cos(x)` — cosine (radians).
217///
218fn math_cos(state: &mut LuaState) -> Result<usize, LuaError> {
219 let x = state.check_number(1)?;
220 state.push(LuaValue::Float(x.cos()));
221 Ok(1)
222}
223
224/// `math.tan(x)` — tangent (radians).
225///
226fn math_tan(state: &mut LuaState) -> Result<usize, LuaError> {
227 let x = state.check_number(1)?;
228 state.push(LuaValue::Float(x.tan()));
229 Ok(1)
230}
231
232/// `math.asin(x)` — arc-sine, result in radians.
233///
234fn math_asin(state: &mut LuaState) -> Result<usize, LuaError> {
235 let x = state.check_number(1)?;
236 state.push(LuaValue::Float(x.asin()));
237 Ok(1)
238}
239
240/// `math.acos(x)` — arc-cosine, result in radians.
241///
242fn math_acos(state: &mut LuaState) -> Result<usize, LuaError> {
243 let x = state.check_number(1)?;
244 state.push(LuaValue::Float(x.acos()));
245 Ok(1)
246}
247
248/// `math.atan(y [, x])` — arc-tangent of y/x (defaults x=1), result in
249/// radians. Subsumes C's `atan2` when x is provided.
250///
251fn math_atan(state: &mut LuaState) -> Result<usize, LuaError> {
252 let y = state.check_number(1)?;
253 let x = state.opt_number(2, 1.0)?;
254 state.push(LuaValue::Float(y.atan2(x)));
255 Ok(1)
256}
257
258/// `math.cosh(x)` — hyperbolic cosine. Deprecated `LUA_COMPAT_MATHLIB`
259/// function, registered only under the 5.3 backend.
260///
261fn math_cosh(state: &mut LuaState) -> Result<usize, LuaError> {
262 let x = state.check_number(1)?;
263 state.push(LuaValue::Float(x.cosh()));
264 Ok(1)
265}
266
267/// `math.sinh(x)` — hyperbolic sine. Deprecated `LUA_COMPAT_MATHLIB`
268/// function, registered only under the 5.3 backend.
269///
270fn math_sinh(state: &mut LuaState) -> Result<usize, LuaError> {
271 let x = state.check_number(1)?;
272 state.push(LuaValue::Float(x.sinh()));
273 Ok(1)
274}
275
276/// `math.tanh(x)` — hyperbolic tangent. Deprecated `LUA_COMPAT_MATHLIB`
277/// function, registered only under the 5.3 backend.
278///
279fn math_tanh(state: &mut LuaState) -> Result<usize, LuaError> {
280 let x = state.check_number(1)?;
281 state.push(LuaValue::Float(x.tanh()));
282 Ok(1)
283}
284
285/// `math.pow(x, y)` — x raised to the power y, always returning a float.
286/// Deprecated `LUA_COMPAT_MATHLIB` function, registered only under the 5.3
287/// backend. Mirrors C `pow(luaL_checknumber, luaL_checknumber)`.
288///
289fn math_pow(state: &mut LuaState) -> Result<usize, LuaError> {
290 let x = state.check_number(1)?;
291 let y = state.check_number(2)?;
292 state.push(LuaValue::Float(x.powf(y)));
293 Ok(1)
294}
295
296/// `math.log10(x)` — base-10 logarithm. Deprecated `LUA_COMPAT_MATHLIB`
297/// function, registered only under the 5.3 backend.
298///
299fn math_log10(state: &mut LuaState) -> Result<usize, LuaError> {
300 let x = state.check_number(1)?;
301 state.push(LuaValue::Float(x.log10()));
302 Ok(1)
303}
304
305/// `math.ldexp(x, e)` — `x * 2^e`. Deprecated `LUA_COMPAT_MATHLIB` function,
306/// registered only under the 5.3 backend. The exponent is an integer argument
307/// truncated to C `int` range, matching `ldexp(x, (int)luaL_checkinteger)`.
308///
309fn math_ldexp(state: &mut LuaState) -> Result<usize, LuaError> {
310 let x = state.check_number(1)?;
311 let e = state.check_integer(2)? as i32;
312 state.push(LuaValue::Float(ldexp(x, e)));
313 Ok(1)
314}
315
316/// Pure `ldexp`: returns `x * 2^exp` with C `ldexp` semantics.
317///
318/// A naive `x * 2f64.powi(exp)` underflows (or overflows) the intermediate
319/// `2^exp` for large-magnitude exponents, losing subnormal results such as
320/// `ldexp(1.0, -1074) == 5e-324`. The scaling is therefore applied in bounded
321/// steps so no intermediate factor under/overflows: each step multiplies by a
322/// power of two whose magnitude stays inside the normal `f64` range.
323fn ldexp(x: f64, exp: i32) -> f64 {
324 if x == 0.0 || !x.is_finite() {
325 return x;
326 }
327 let mut result = x;
328 let mut e = exp;
329 // 2^1023 is the largest power of two representable as a normal f64; chunk
330 // the exponent so each `from_bits` factor is always finite and nonzero.
331 while e > 1023 {
332 result *= f64::from_bits(0x7feu64 << 52); // 2^1023
333 e -= 1023;
334 }
335 while e < -1022 {
336 result *= f64::from_bits(0x001u64 << 52); // 2^-1022 (smallest normal)
337 e += 1022;
338 }
339 result * f64::from_bits(((e + 1023) as u64) << 52)
340}
341
342/// `math.frexp(x)` — split x into a normalized mantissa and an exponent such
343/// that `x == mantissa * 2^exponent` with `0.5 <= |mantissa| < 1`. Returns the
344/// float mantissa followed by the **integer** exponent, matching C
345/// `frexp` + `lua_pushinteger`. Deprecated `LUA_COMPAT_MATHLIB` function,
346/// registered only under the 5.3 backend.
347///
348/// Rust std has no `frexp`; this replicates C `frexp` via `f64` bit
349/// manipulation, including the `frexp(0.0) == (0.0, 0)` special case (and the
350/// matching `-0.0`, infinity, and NaN cases, which C leaves unchanged with a
351/// zero exponent).
352fn math_frexp(state: &mut LuaState) -> Result<usize, LuaError> {
353 let x = state.check_number(1)?;
354 let (mantissa, exponent) = frexp(x);
355 state.push(LuaValue::Float(mantissa));
356 state.push(LuaValue::Int(exponent as i64));
357 Ok(2)
358}
359
360/// Pure `frexp`: returns `(mantissa, exponent)` with `x == mantissa * 2^exp`.
361///
362/// Replicates C `frexp` semantics for f64. Zero, infinity, and NaN are
363/// returned unchanged with a zero exponent.
364fn frexp(x: f64) -> (f64, i32) {
365 if x == 0.0 || !x.is_finite() {
366 return (x, 0);
367 }
368 let bits = x.to_bits();
369 let raw_exp = ((bits >> 52) & 0x7ff) as i32;
370 if raw_exp == 0 {
371 // Subnormal: scale up by 2^54 to normalize, then correct the exponent.
372 let (m, e) = frexp(x * (1u64 << 54) as f64);
373 return (m, e - 54);
374 }
375 // Bias the exponent so the mantissa lands in [0.5, 1): set the stored
376 // exponent field to 0x3fe (unbiased -1).
377 let exponent = raw_exp - 1022;
378 let mantissa_bits = (bits & !(0x7ffu64 << 52)) | (0x3feu64 << 52);
379 (f64::from_bits(mantissa_bits), exponent)
380}
381
382/// `math.tointeger(x)` — return `x` as an integer, or the fail value when it
383/// has no exact integer representation.
384///
385/// The fail value is `nil`, not `false`: the default 5.3/5.4/5.5 builds expand
386/// `luaL_pushfail` to a nil push (only a `LUA_FAILISFALSE` build would push
387/// `false`), and the oracle contract pins the nil.
388fn math_toint(state: &mut LuaState) -> Result<usize, LuaError> {
389 if let Some(n) = state.to_integer_opt(1) {
390 state.push(LuaValue::Int(n));
391 } else {
392 state.check_any(1)?;
393 state.push(LuaValue::Nil);
394 }
395 Ok(1)
396}
397
398/// `math.floor(x)` — largest integer ≤ x.
399///
400fn math_floor(state: &mut LuaState) -> Result<usize, LuaError> {
401 if arg_is_int(state, 1) {
402 keep_first_arg(state)?;
403 } else {
404 let d = state.check_number(1)?.floor();
405 push_int_or_float(state, d);
406 }
407 Ok(1)
408}
409
410/// `math.ceil(x)` — smallest integer ≥ x.
411///
412fn math_ceil(state: &mut LuaState) -> Result<usize, LuaError> {
413 if arg_is_int(state, 1) {
414 keep_first_arg(state)?;
415 } else {
416 let d = state.check_number(1)?.ceil();
417 push_int_or_float(state, d);
418 }
419 Ok(1)
420}
421
422/// `math.fmod(x, y)` — floating-point remainder (same sign as x).
423///
424fn math_fmod(state: &mut LuaState) -> Result<usize, LuaError> {
425 if arg_is_int(state, 1) && arg_is_int(state, 2) {
426 let a = state.to_integer(1).unwrap_or(0);
427 let d = state.to_integer(2).unwrap_or(0);
428 if (d as u64).wrapping_add(1) <= 1 {
429 if d == 0 {
430 return Err(lua_vm::debug::arg_error_impl(state, 2, b"zero"));
431 }
432 state.push(LuaValue::Int(0));
433 } else {
434 state.push(LuaValue::Int(a % d));
435 }
436 } else {
437 let x = state.check_number(1)?;
438 let y = state.check_number(2)?;
439 state.push(LuaValue::Float(x % y));
440 }
441 Ok(1)
442}
443
444/// `math.modf(x)` — split into integer and fractional parts; returns both.
445///
446/// The integer part is computed with `ceil`/`floor` + subtraction rather than
447/// libc `modf`, avoiding a `double *` out-parameter ABI. An integer argument is
448/// its own integer part with a `0.0` fractional part.
449fn math_modf(state: &mut LuaState) -> Result<usize, LuaError> {
450 if arg_is_int(state, 1) {
451 keep_first_arg(state)?;
452 state.push(LuaValue::Float(0.0));
453 } else {
454 let n = state.check_number(1)?;
455 let ip = if n < 0.0 { n.ceil() } else { n.floor() };
456 push_int_or_float(state, ip);
457 let frac = if n == ip { 0.0 } else { n - ip };
458 state.push(LuaValue::Float(frac));
459 }
460 Ok(2)
461}
462
463/// `math.sqrt(x)` — square root.
464///
465fn math_sqrt(state: &mut LuaState) -> Result<usize, LuaError> {
466 let x = state.check_number(1)?;
467 state.push(LuaValue::Float(x.sqrt()));
468 Ok(1)
469}
470
471/// `math.ult(m, n)` — unsigned less-than on integers.
472///
473fn math_ult(state: &mut LuaState) -> Result<usize, LuaError> {
474 let a = state.check_integer(1)?;
475 let b = state.check_integer(2)?;
476 state.push(LuaValue::Bool((a as u64) < (b as u64)));
477 Ok(1)
478}
479
480/// `math.log(x [, base])` — logarithm; natural if base omitted.
481///
482fn math_log(state: &mut LuaState) -> Result<usize, LuaError> {
483 let x = state.check_number(1)?;
484 // Lua 5.1's `math.log` takes a single argument and silently ignores any
485 // second; the two-argument base form is a 5.2 addition. Verified against
486 // lua5.1.5: `math.log(8,2) == math.log(8) == ln(8)`, and a second arg never
487 // errors. See specs/followup/5.1-roster-syntax.md §1.
488 if matches!(state.global().lua_version, lua_types::LuaVersion::V51) {
489 state.push(LuaValue::Float(x.ln()));
490 return Ok(1);
491 }
492 let res = if matches!(state.type_at(2), LuaType::None | LuaType::Nil) {
493 x.ln()
494 } else {
495 let base = state.check_number(2)?;
496 if base == 2.0 {
497 x.log2()
498 } else if base == 10.0 {
499 x.log10()
500 } else {
501 x.ln() / base.ln()
502 }
503 };
504 state.push(LuaValue::Float(res));
505 Ok(1)
506}
507
508/// `math.exp(x)` — e raised to the power x.
509///
510fn math_exp(state: &mut LuaState) -> Result<usize, LuaError> {
511 let x = state.check_number(1)?;
512 state.push(LuaValue::Float(x.exp()));
513 Ok(1)
514}
515
516/// `math.deg(x)` — convert radians to degrees.
517///
518fn math_deg(state: &mut LuaState) -> Result<usize, LuaError> {
519 let x = state.check_number(1)?;
520 state.push(LuaValue::Float(x * (180.0 / PI)));
521 Ok(1)
522}
523
524/// `math.rad(x)` — convert degrees to radians.
525///
526fn math_rad(state: &mut LuaState) -> Result<usize, LuaError> {
527 let x = state.check_number(1)?;
528 state.push(LuaValue::Float(x * (PI / 180.0)));
529 Ok(1)
530}
531
532/// Whether `math.max`/`math.min` use the float-only `luaL_checknumber` path.
533///
534/// 5.1 and 5.2 coerce every argument with `luaL_checknumber`, comparing
535/// `lua_Number` doubles and returning the coerced number — so a non-number
536/// argument raises `number expected, got <type>` and a number-shaped string
537/// argument is accepted and returned as a number. 5.3+ rewrote both to use
538/// `lua_compare(..., LUA_OPLT)`, which compares the values directly (so strings
539/// compare lexicographically and are returned unchanged). 5.4 is the
540/// unchangeable baseline, so the float-only path is gated to V51/V52 only.
541fn max_min_is_float_only(state: &LuaState) -> bool {
542 matches!(
543 state.global().lua_version,
544 lua_types::LuaVersion::V51 | lua_types::LuaVersion::V52
545 )
546}
547
548/// `math.min(x, ...)` — minimum of all arguments.
549///
550/// On 5.1/5.2 every argument is coerced via `luaL_checknumber` and the smallest
551/// number is returned (see [`max_min_is_float_only`]); on 5.3+ the smallest
552/// argument by Lua `<` comparison is returned unchanged.
553fn math_min(state: &mut LuaState) -> Result<usize, LuaError> {
554 let n = state.get_top();
555 if n < 1 {
556 return Err(lua_vm::debug::arg_error_impl(state, 1, b"value expected"));
557 }
558 if max_min_is_float_only(state) {
559 let mut dmin = state.check_number(1)?;
560 for i in 2..=n {
561 let d = state.check_number(i)?;
562 if d < dmin {
563 dmin = d;
564 }
565 }
566 state.push(LuaValue::Float(dmin));
567 return Ok(1);
568 }
569 let mut imin: i32 = 1;
570 for i in 2..=n {
571 if state.compare_lt(i, imin)? {
572 imin = i;
573 }
574 }
575 state.push_value(imin)?;
576 Ok(1)
577}
578
579/// `math.max(x, ...)` — maximum of all arguments.
580///
581/// On 5.1/5.2 every argument is coerced via `luaL_checknumber` and the largest
582/// number is returned (see [`max_min_is_float_only`]); on 5.3+ the largest
583/// argument by Lua `<` comparison is returned unchanged.
584fn math_max(state: &mut LuaState) -> Result<usize, LuaError> {
585 let n = state.get_top();
586 if n < 1 {
587 return Err(lua_vm::debug::arg_error_impl(state, 1, b"value expected"));
588 }
589 if max_min_is_float_only(state) {
590 let mut dmax = state.check_number(1)?;
591 for i in 2..=n {
592 let d = state.check_number(i)?;
593 if d > dmax {
594 dmax = d;
595 }
596 }
597 state.push(LuaValue::Float(dmax));
598 return Ok(1);
599 }
600 let mut imax: i32 = 1;
601 for i in 2..=n {
602 if state.compare_lt(imax, i)? {
603 imax = i;
604 }
605 }
606 state.push_value(imax)?;
607 Ok(1)
608}
609
610/// `math.type(x)` — return `"integer"`, `"float"`, or the fail value for
611/// non-numbers.
612///
613/// The non-number fail value is `nil`, not `false`, for the same reason as
614/// [`math_toint`]: `luaL_pushfail` pushes nil in the default builds and the
615/// oracle contract pins the nil.
616fn math_type(state: &mut LuaState) -> Result<usize, LuaError> {
617 if matches!(state.type_at(1), LuaType::Number) {
618 if arg_is_int(state, 1) {
619 state.push_string(b"integer")?;
620 } else {
621 state.push_string(b"float")?;
622 }
623 } else {
624 state.check_any(1)?;
625 state.push(LuaValue::Nil);
626 }
627 Ok(1)
628}
629
630// ── PRNG-backed Lua functions ─────────────────────────────────────────────
631
632/// `math.random([m [, n]])` — pseudo-random number generation.
633///
634/// With no arguments: float in `[0, 1)`. With one argument `n`: integer in
635/// `[1, n]` (or a full-range `u64` when `n == 0`, on 5.4/5.5). With two
636/// arguments `m, n`: integer in `[m, n]`. The PRNG word is advanced first, then
637/// the arguments are read via a separate borrow.
638///
639/// TODO: the PRNG state is a thread-local rather than per-`lua_State` typed
640/// userdata in closure upvalue 1; migrating it to `GcRef<RefCell<RanState>>`
641/// (so each state has its own generator) is deferred to the upvalue-userdata
642/// work, not changed here.
643fn math_random(state: &mut LuaState) -> Result<usize, LuaError> {
644 let rv = advance_prng(state)?;
645 let n_args = state.get_top();
646
647 if n_args == 0 {
648 state.push(LuaValue::Float(xoshiro::rand_to_float(rv)));
649 return Ok(1);
650 }
651
652 let version = state.global().lua_version;
653 let is_v53 = version == lua_types::LuaVersion::V53;
654 // 5.1/5.2 are float-only and use the C `rand()` contract: there is no
655 // `random(0)` full-range special case (that is a 5.4/5.5 addition), the
656 // empty-interval error for `random(m, n)` reports argument index 2 (the
657 // upper bound), and integer-valued results are pushed as `Float` to honour
658 // the never-construct-`Int` invariant under `FloatOnly`. See
659 // specs/followup/5.1-numbers-prng.md §"Impl seams".
660 let float_only = version.number_model() == lua_types::NumberModel::FloatOnly;
661
662 let (low, up, empty_arg) = match n_args {
663 1 => {
664 let up = state.check_integer(1)?;
665 // 5.4/5.5 `random(0)` returns a full-range integer; 5.1/5.2/5.3 have
666 // no such special case — it is `[1, 0]`, an empty interval.
667 if up == 0 && !is_v53 && !float_only {
668 state.push(LuaValue::Int(rv as i64));
669 return Ok(1);
670 }
671 (1i64, up, 1)
672 }
673 2 => {
674 let low = state.check_integer(1)?;
675 let up = state.check_integer(2)?;
676 // 5.1's `luaL_checkint(L, 2)` for the upper bound means its
677 // empty-interval `luaL_argerror` reports argument #2; the modern
678 // bodies report #1.
679 let empty_arg = if float_only { 2 } else { 1 };
680 (low, up, empty_arg)
681 }
682 _ => {
683 return Err(LuaError::runtime(format_args!("wrong number of arguments")));
684 }
685 };
686
687 if low > up {
688 return Err(lua_vm::debug::arg_error_impl(
689 state,
690 empty_arg,
691 b"interval is empty",
692 ));
693 }
694
695 // 5.3 `math_random` rejects intervals whose width overflows a signed integer
696 // (`low >= 0 || up <= LUA_MAXINTEGER + low`). 5.4/5.5 use the `project`
697 // bit-mask algorithm, which handles the full range without erroring.
698 if is_v53 && !(low >= 0 || up <= i64::MAX.wrapping_add(low)) {
699 return Err(lua_vm::debug::arg_error_impl(
700 state,
701 1,
702 b"interval too large",
703 ));
704 }
705
706 let range = (up as u64).wrapping_sub(low as u64);
707 let p = project_from_upvalue(state, rv, range)?;
708 let result = (p as u64).wrapping_add(low as u64) as i64;
709 if float_only {
710 state.push(LuaValue::Float(result as f64));
711 } else {
712 state.push(LuaValue::Int(result));
713 }
714 Ok(1)
715}
716
717/// `math.randomseed([x [, y]])` — seed the PRNG.
718///
719/// The return shape and auto-seed behavior are version-gated and load-bearing:
720/// - 5.1/5.2 (float-only) REQUIRE the seed argument (a missing one raises
721/// "number expected, got no value"), take a single seed word, and return NO
722/// values.
723/// - 5.3+ auto-seed from host entropy when the argument is absent and return the
724/// two seed words.
725///
726/// See `specs/followup/5.1-numbers-prng.md`.
727fn math_randomseed(state: &mut LuaState) -> Result<usize, LuaError> {
728 let float_only = state.global().lua_version.number_model() == lua_types::NumberModel::FloatOnly;
729
730 if matches!(state.type_at(1), LuaType::None) {
731 if float_only {
732 let n1 = state.check_integer(1)? as u64;
733 apply_set_seed_quiet(state, n1, 0);
734 return Ok(0);
735 }
736 apply_random_seed(state)?;
737 } else {
738 let n1 = state.check_integer(1)? as u64;
739 if float_only {
740 apply_set_seed_quiet(state, n1, 0);
741 return Ok(0);
742 }
743 let n2 = state.opt_integer(2, 0)? as u64;
744 apply_set_seed(state, n1, n2)?;
745 }
746 Ok(2)
747}
748
749/// Advance the PRNG in the thread-local [`RAN_STATE`] and return the raw 64-bit
750/// output.
751///
752/// The thread-local is the only difference from the C source, which draws from
753/// userdata in closure upvalue 1; the [`next_rand`] algorithm is identical (see
754/// [`math_random`] for the deferred per-state migration).
755fn advance_prng(_state: &mut LuaState) -> Result<u64, LuaError> {
756 Ok(RAN_STATE.with(|r| xoshiro::next_rand(&mut r.borrow_mut().s)))
757}
758
759/// Project a raw draw into `[0, n]` using the thread-local PRNG for any
760/// rejection redraws (see [`advance_prng`] for the thread-local rationale).
761fn project_from_upvalue(_state: &mut LuaState, ran: u64, n: u64) -> Result<u64, LuaError> {
762 Ok(RAN_STATE.with(|r| xoshiro::project(ran, n, &mut r.borrow_mut().s)))
763}
764
765/// Seed the PRNG from the host entropy hook (the 5.3+ auto-seed path).
766///
767/// The second seed word is derived deterministically from the entropy value;
768/// the C source additionally mixes address entropy, which is deferred until a
769/// richer host entropy API exists.
770fn apply_random_seed(state: &mut LuaState) -> Result<(), LuaError> {
771 let entropy = state.global().entropy_hook.map(|hook| hook()).unwrap_or(0);
772 let seed1 = entropy;
773 let seed2: u64 = entropy.rotate_left(17) ^ 0x9e37_79b9_7f4a_7c15;
774 apply_set_seed(state, seed1, seed2)
775}
776
777/// Apply explicit seeds to the thread-local PRNG and push them onto the stack
778/// (the 5.3+ `randomseed` return shape — the two seed words).
779fn apply_set_seed(state: &mut LuaState, n1: u64, n2: u64) -> Result<(), LuaError> {
780 RAN_STATE.with(|r| xoshiro::set_seed_words(&mut r.borrow_mut().s, n1, n2));
781 state.push(LuaValue::Int(n1 as i64));
782 state.push(LuaValue::Int(n2 as i64));
783 Ok(())
784}
785
786/// Seed the PRNG without pushing the seed words onto the stack.
787///
788/// 5.1/5.2 `math.randomseed` returns no values, so its seeding path must not
789/// push (unlike the modern [`apply_set_seed`], which returns the two words).
790fn apply_set_seed_quiet(_state: &mut LuaState, n1: u64, n2: u64) {
791 RAN_STATE.with(|r| xoshiro::set_seed_words(&mut r.borrow_mut().s, n1, n2));
792}
793
794/// Register `math.random` and `math.randomseed` on the math library table at
795/// stack top, after seeding the PRNG.
796///
797/// C binds the PRNG state as upvalue 1 of both closures; with the PRNG in a
798/// thread-local ([`RAN_STATE`]) these are registered as plain (non-closure)
799/// entries instead.
800fn set_rand_func(state: &mut LuaState) -> Result<(), LuaError> {
801 apply_random_seed(state)?;
802 state.pop_n(2);
803
804 state.push_c_function(math_random)?;
805 state.set_field(-2, b"random")?;
806 state.push_c_function(math_randomseed)?;
807 state.set_field(-2, b"randomseed")?;
808 Ok(())
809}
810
811// ── Library registration table ────────────────────────────────────────────
812
813static MATHLIB_FUNCS: &[(&[u8], LuaCFunction)] = &[
814 (b"abs", math_abs),
815 (b"acos", math_acos),
816 (b"asin", math_asin),
817 (b"atan", math_atan),
818 (b"ceil", math_ceil),
819 (b"cos", math_cos),
820 (b"deg", math_deg),
821 (b"exp", math_exp),
822 (b"tointeger", math_toint),
823 (b"floor", math_floor),
824 (b"fmod", math_fmod),
825 (b"ult", math_ult),
826 (b"log", math_log),
827 (b"max", math_max),
828 (b"min", math_min),
829 (b"modf", math_modf),
830 (b"rad", math_rad),
831 (b"sin", math_sin),
832 (b"sqrt", math_sqrt),
833 (b"tan", math_tan),
834 (b"type", math_type),
835 // `frexp`/`ldexp` survive into 5.5, unlike the rest of the compat roster:
836 // they are registered unconditionally on 5.3/5.4/5.5. Verified against all
837 // three reference binaries (`type(math.frexp)`/`type(math.ldexp)` ==
838 // "function" on 5.3.6, 5.4.7, 5.5.0).
839 (b"frexp", math_frexp),
840 (b"ldexp", math_ldexp),
841];
842
843// ── Module entry point ────────────────────────────────────────────────────
844
845/// Open the `math` library: create the table, register the version-agnostic
846/// functions and the per-version roster delta, populate the constants, and wire
847/// up the PRNG functions.
848pub fn luaopen_math(state: &mut LuaState) -> Result<usize, LuaError> {
849 state.new_lib(MATHLIB_FUNCS)?;
850
851 // Per-version roster delta: the `LUA_COMPAT_MATHLIB`-gated functions
852 // (`atan2` as an alias of `math_atan`, plus cosh/sinh/tanh/pow/log10) ship
853 // in the default lua5.3.6 build (`LUA_COMPAT_MATHLIB` on) AND the default
854 // lua5.4.7 build (its `LUA_COMPAT_5_3` umbrella turns `LUA_COMPAT_MATHLIB`
855 // on), but were dropped in lua5.5.0 (macro commented out). Verified by
856 // probing all three reference binaries directly. `frexp`/`ldexp` are NOT in
857 // this set — they survive into 5.5 and live in the agnostic roster above.
858 // `new_lib` leaves the new table on the stack top, so we register into it
859 // directly. See `specs/followup/5.3-math.md` (whose 5.4/5.5-absence claim
860 // is corrected here against the binaries, the binding oracle).
861 // The `LUA_COMPAT_MATHLIB` deprecated roster also ships in the default
862 // lua5.2.4 build (verified against the reference binary: `type(math.atan2)`
863 // etc. == "function" on 5.2.4). 5.5 drops them.
864 if matches!(
865 state.global().lua_version,
866 lua_types::LuaVersion::V51
867 | lua_types::LuaVersion::V52
868 | lua_types::LuaVersion::V53
869 | lua_types::LuaVersion::V54
870 ) {
871 const COMPAT_MATH_FUNCS: &[(&[u8], LuaCFunction)] = &[
872 (b"atan2", math_atan),
873 (b"cosh", math_cosh),
874 (b"sinh", math_sinh),
875 (b"tanh", math_tanh),
876 (b"pow", math_pow),
877 (b"log10", math_log10),
878 ];
879 state.set_funcs_with_upvalues(COMPAT_MATH_FUNCS, 0)?;
880 }
881
882 // Lua 5.1 carries `math.mod`, a compat alias of `fmod` predating the rename
883 // (`math.mod(7,3) == 1`). It was removed in 5.2. Verified against
884 // lua5.1.5: `type(math.mod)` == "function". See
885 // specs/followup/5.1-roster-syntax.md §1.
886 if matches!(state.global().lua_version, lua_types::LuaVersion::V51) {
887 state.push_c_function(math_fmod)?;
888 state.set_field(-2, b"mod")?;
889 }
890
891 state.push(LuaValue::Float(PI));
892 state.set_field(-2, b"pi")?;
893
894 state.push(LuaValue::Float(f64::INFINITY));
895 state.set_field(-2, b"huge")?;
896
897 // LUA_MAXINTEGER = i64::MAX (lua_Integer is int64_t in default config).
898 state.push(LuaValue::Int(i64::MAX));
899 state.set_field(-2, b"maxinteger")?;
900
901 state.push(LuaValue::Int(i64::MIN));
902 state.set_field(-2, b"mininteger")?;
903
904 // Lua 5.1/5.2 are float-only: the integer-subtype helpers (`math.type`,
905 // `math.tointeger`, `math.ult`) and the integer bounds
906 // (`math.maxinteger`/`mininteger`) are 5.3 additions and are absent there.
907 // Verified against lua5.2.4: each is `nil`.
908 if matches!(
909 state.global().lua_version,
910 lua_types::LuaVersion::V51 | lua_types::LuaVersion::V52
911 ) {
912 for field in [
913 &b"type"[..],
914 &b"tointeger"[..],
915 &b"ult"[..],
916 &b"maxinteger"[..],
917 &b"mininteger"[..],
918 ] {
919 state.push(LuaValue::Nil);
920 state.set_field(-2, field)?;
921 }
922 }
923
924 set_rand_func(state)?;
925
926 Ok(1)
927}