zsh/ported/modules/mathfunc.rs
1//! Mathematical functions for arithmetic expressions — port of
2//! `Src/Modules/mathfunc.c`.
3//!
4//! C source has THREE anonymous `enum {}` blocks (lines 35, 90,
5//! 104) generating `int`-typed constants — no named C type, so
6//! the Rust port mirrors them as `pub const ... : i32 = ...;`
7//! definitions only (rule 1: no Rust-only struct/enum types).
8//!
9//! All math-fn dispatch lives in a single `math_func()` switch,
10//! matching the C structure 1:1.
11
12#![allow(clippy::approx_constant)]
13
14use crate::ported::math::{mnumber, MN_FLOAT, MN_INTEGER};
15
16// libm bindings used by the math-function dispatcher. Direct port
17// of the calls C's `math_func()` (Src/Modules/mathfunc.c:172-436)
18// makes via `<math.h>`. Bessel functions and `erf` aren't in
19// Rust's `std`, so we declare the C ABI bindings here.
20#[cfg(unix)]
21extern "C" {
22 fn j0(x: f64) -> f64;
23 fn j1(x: f64) -> f64;
24 fn jn(n: i32, x: f64) -> f64;
25 fn y0(x: f64) -> f64;
26 fn y1(x: f64) -> f64;
27 fn yn(n: i32, x: f64) -> f64;
28 fn erf(x: f64) -> f64;
29 fn erfc(x: f64) -> f64;
30 fn lgamma(x: f64) -> f64;
31 fn tgamma(x: f64) -> f64;
32 fn ilogb(x: f64) -> i32;
33 fn logb(x: f64) -> f64;
34 fn nextafter(x: f64, y: f64) -> f64;
35 fn rint(x: f64) -> f64;
36 fn scalbn(x: f64, n: i32) -> f64;
37 fn ldexp(x: f64, exp: i32) -> f64;
38 fn copysign(x: f64, y: f64) -> f64;
39 fn expm1(x: f64) -> f64;
40 fn log1p(x: f64) -> f64;
41 fn cbrt(x: f64) -> f64;
42}
43
44/// Port of `math_string(UNUSED(char *name), char *arg, int id)` from `Src/Modules/mathfunc.c:439`. The
45/// string-arg math-fn dispatcher behind `rand48("seedvar")` and
46/// future string-takers. C signature:
47/// `static mnumber math_string(char *name, char *arg, int id)`
48///
49/// Strips leading/trailing iblank from `arg` (mathfunc.c:447-451)
50/// then switches on `id`. Currently only `MS_RAND48` exists; the
51/// random-bit production lives in `crate::ported::random` and
52/// `crate::ported::modules::random_real`. Returns `zero_mnumber`
53/// for unrecognised ids (matching C's pre-init `ret = zero_mnumber`).
54#[allow(unused_variables)]
55pub fn math_string(name: &str, arg: &str, id: i32) -> mnumber { // c:439
56 let trimmed = arg.trim_matches(|c: char| c == ' ' || c == '\t'); // c:439-451 iblank
57 match id {
58 MS_RAND48 => { // c:457
59 // C decodes optional 12-hex seedstr from $seedvar then
60 // calls erand48(). zshrs uses `random_real()` which
61 // already produces uniform doubles via the OS-entropy
62 // path; the seed-from-param wiring is pending param-
63 // table integration.
64 let _ = trimmed;
65 mnumber {
66 l: 0,
67 d: crate::ported::modules::random_real::random_real(),
68 type_: MN_FLOAT,
69 }
70 }
71 _ => mnumber { l: 0, d: 0.0, type_: MN_INTEGER }, // zero_mnumber
72 }
73}
74
75// `mftab` — port of `static struct mathfunc mftab[]` (mathfunc.c:497).
76
77
78// `module_features` — port of `static struct features module_features`
79// from mathfunc.c:540.
80
81
82
83/// Port of `setup_(UNUSED(Module m))` from `Src/Modules/mathfunc.c:548`.
84#[allow(unused_variables)]
85pub fn setup_(m: *const module) -> i32 { // c:548
86 // C body c:550-551 — `return 0`. Faithful empty-body port.
87 0
88}
89
90/// Port of `features_(UNUSED(Module m), UNUSED(char ***features))` from `Src/Modules/mathfunc.c:555`.
91/// C body: `*features = featuresarray(m, &module_features); return 0;`
92pub fn features_(m: *const module, features: &mut Vec<String>) -> i32 { // c:555
93 *features = featuresarray(m, module_features());
94 0 // c:570
95}
96
97/// Port of `enables_(UNUSED(Module m), UNUSED(int **enables))` from `Src/Modules/mathfunc.c:563`.
98/// C body: `return handlefeatures(m, &module_features, enables);`
99pub fn enables_(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 { // c:563
100 handlefeatures(m, module_features(), enables) // c:570
101}
102
103/// Port of `boot_(UNUSED(Module m))` from `Src/Modules/mathfunc.c:570`.
104#[allow(unused_variables)]
105pub fn boot_(m: *const module) -> i32 { // c:570
106 // C body c:572-573 — `return 0`. Faithful empty-body port; the
107 // math functions are registered via the mf_list
108 // feature dispatch, no extra boot work needed.
109 0
110}
111
112/// Port of `cleanup_(UNUSED(Module m))` from `Src/Modules/mathfunc.c:577`.
113/// C body: `return setfeatureenables(m, &module_features, NULL);`
114pub fn cleanup_(m: *const module) -> i32 { // c:577
115 setfeatureenables(m, module_features(), None) // c:584
116}
117
118/// Port of `finish_(UNUSED(Module m))` from `Src/Modules/mathfunc.c:584`.
119#[allow(unused_variables)]
120pub fn finish_(m: *const module) -> i32 { // c:584
121 // C body c:586-587 — `return 0`. Faithful empty-body port; the
122 // math functions are unregistered via cleanup_.
123 0
124}
125
126// ============================================================
127// MF_* — port of the anonymous `enum {}` at mathfunc.c:34-84.
128// C `enum {}` with no typedef → untyped int constants. Rust
129// mirrors as `pub const ... : i32` (no Rust-only enum type).
130// ============================================================
131pub const MF_ABS: i32 = 0; // c:35
132pub const MF_ACOS: i32 = 1; // c:36
133pub const MF_ACOSH: i32 = 2;
134pub const MF_ASIN: i32 = 3;
135pub const MF_ASINH: i32 = 4;
136pub const MF_ATAN: i32 = 5;
137pub const MF_ATANH: i32 = 6;
138pub const MF_CBRT: i32 = 7;
139pub const MF_CEIL: i32 = 8;
140pub const MF_COPYSIGN: i32 = 9;
141pub const MF_COS: i32 = 10;
142pub const MF_COSH: i32 = 11;
143pub const MF_ERF: i32 = 12;
144pub const MF_ERFC: i32 = 13;
145pub const MF_EXP: i32 = 14;
146pub const MF_EXPM1: i32 = 15;
147pub const MF_FABS: i32 = 16;
148pub const MF_FLOAT: i32 = 17;
149pub const MF_FLOOR: i32 = 18;
150pub const MF_FMOD: i32 = 19;
151pub const MF_GAMMA: i32 = 20;
152pub const MF_HYPOT: i32 = 21;
153pub const MF_ILOGB: i32 = 22;
154pub const MF_INT: i32 = 23;
155pub const MF_ISINF: i32 = 24;
156pub const MF_ISNAN: i32 = 25;
157pub const MF_J0: i32 = 26;
158pub const MF_J1: i32 = 27;
159pub const MF_JN: i32 = 28;
160pub const MF_LDEXP: i32 = 29;
161pub const MF_LGAMMA: i32 = 30;
162pub const MF_LOG: i32 = 31;
163pub const MF_LOG10: i32 = 32;
164pub const MF_LOG1P: i32 = 33;
165pub const MF_LOG2: i32 = 34;
166pub const MF_LOGB: i32 = 35;
167pub const MF_NEXTAFTER: i32 = 36;
168pub const MF_RINT: i32 = 37;
169pub const MF_SCALB: i32 = 38;
170pub const MF_SIGNGAM: i32 = 39; // c:75 #ifdef HAVE_SIGNGAM
171pub const MF_SIN: i32 = 40;
172pub const MF_SINH: i32 = 41;
173pub const MF_SQRT: i32 = 42;
174pub const MF_TAN: i32 = 43;
175pub const MF_TANH: i32 = 44;
176pub const MF_Y0: i32 = 45;
177pub const MF_Y1: i32 = 46;
178pub const MF_YN: i32 = 47; // c:84
179
180
181
182// =====================================================================
183// static struct mathfunc mftab[] c:497
184// static struct features module_features c:540
185// =====================================================================
186
187use crate::ported::zsh_h::module;
188
189// ============================================================
190// MS_* — port of the anonymous `enum {}` at mathfunc.c:90.
191// String-arg math-fn ids.
192// ============================================================
193pub const MS_RAND48: i32 = 0; // c:91
194
195// ============================================================
196// TF_* — port of the anonymous `enum {}` at mathfunc.c:104.
197// Type-flag bits, individually testable.
198// ============================================================
199pub const TF_NOCONV: i32 = 1; // c:106 don't convert to float
200pub const TF_INT1: i32 = 2; // c:107 first arg is integer
201pub const TF_INT2: i32 = 4; // c:108 second arg is integer
202pub const TF_NOASS: i32 = 8; // c:109 don't assign result as double
203
204/// Port of the `TFLAG(x)` macro from `mathfunc.c:113`.
205/// `#define TFLAG(x) ((x) << 8)`. Shifts the type-flag bits into
206/// the high byte of the `id` arg passed to `math_func()` so the
207/// MF_* numeric ids can occupy the low byte.
208pub const fn tflag(x: i32) -> i32 { x << 8 } // c:113
209
210/// Port of `math_func(UNUSED(char *name), int argc, mnumber *argv, int id)` from `Src/Modules/mathfunc.c:173`. The
211/// dispatcher behind every numeric math fn registered via
212/// `NUMMATHFUNC` in `mftab[]` (mathfunc.c:115-167).
213///
214/// C signature:
215/// `static mnumber math_func(char *name, int argc, mnumber *argv, int id)`
216///
217/// Matches that signature exactly: `name` is unused (UNUSED in C);
218/// `argc` is the actual argument count; `argv` is the slice of
219/// argument values; `id` is the MF_* function id ORed with TFLAG()
220/// type flags in its high byte.
221#[allow(non_snake_case)]
222/// WARNING: param names don't match C — Rust=(_name, argc, argv, id) vs C=(name, argc, argv, id)
223pub fn math_func(_name: &str, argc: i32, argv: &[mnumber], id: i32) -> mnumber { // c:173
224 let mut ret = mnumber { l: 0, d: 0.0, type_: MN_FLOAT }; // c:173,193
225 let mut argd: f64 = 0.0; // c:175
226 let mut argd2: f64 = 0.0; // c:175
227 let mut argi: i32 = 0; // c:176
228
229 // Type-coerce argv[0] (and argv[1]) per the TF_INT1/TF_INT2/
230 // TF_NOCONV flag bits — c:178-191.
231 if argc > 0 && (id & tflag(TF_NOCONV)) == 0 { // c:178
232 if (id & tflag(TF_INT1)) != 0 { // c:179
233 argi = if argv[0].type_ == MN_FLOAT {
234 argv[0].d as i32 // c:180
235 } else {
236 argv[0].l as i32
237 };
238 } else { // c:181
239 argd = if argv[0].type_ == MN_INTEGER {
240 argv[0].l as f64 // c:182
241 } else {
242 argv[0].d
243 };
244 }
245 if argc > 1 { // c:183
246 if (id & tflag(TF_INT2)) != 0 { // c:184
247 argi = if argv[1].type_ == MN_FLOAT {
248 argv[1].d as i32 // c:185
249 } else {
250 argv[1].l as i32
251 };
252 } else { // c:187
253 argd2 = if argv[1].type_ == MN_INTEGER {
254 argv[1].l as f64 // c:188
255 } else {
256 argv[1].d
257 };
258 }
259 }
260 }
261
262 // C: `if (errflag) return ret;` — c:196. zshrs's errflag is on
263 // the executor; this dispatcher is invoked from the math
264 // evaluator which already short-circuits on error, so the
265 // explicit check is redundant here.
266
267 let mut retd: f64 = 0.0; // c:175
268
269 match id & 0xff { // c:198
270 MF_ABS => { // c:199
271 ret.type_ = argv[0].type_;
272 if argv[0].type_ == MN_INTEGER {
273 ret.l = if argv[0].l < 0 { -argv[0].l } else { argv[0].l };
274 } else {
275 ret.d = argv[0].d.abs();
276 }
277 }
278 MF_ACOS => retd = argd.acos(), // c:208
279 MF_ACOSH => retd = argd.acosh(), // c:212
280 MF_ASIN => retd = argd.asin(), // c:216
281 MF_ASINH => retd = argd.asinh(), // c:220
282 MF_ATAN => { // c:224
283 retd = if argc == 2 { argd.atan2(argd2) } else { argd.atan() };
284 }
285 MF_ATANH => retd = argd.atanh(), // c:233
286 MF_CBRT => retd = unsafe { cbrt(argd) }, // c:237
287 MF_CEIL => retd = argd.ceil(), // c:241
288 MF_COPYSIGN => retd = unsafe { copysign(argd, argd2) }, // c:245
289 MF_COS => retd = argd.cos(), // c:249
290 MF_COSH => retd = argd.cosh(), // c:253
291 MF_ERF => retd = unsafe { erf(argd) }, // c:257
292 MF_ERFC => retd = unsafe { erfc(argd) }, // c:261
293 MF_EXP => retd = argd.exp(), // c:265
294 MF_EXPM1 => retd = unsafe { expm1(argd) }, // c:269
295 MF_FABS => retd = argd.abs(), // c:273
296 MF_FLOAT => retd = argd, // c:277
297 MF_FLOOR => retd = argd.floor(), // c:281
298 MF_FMOD => retd = argd % argd2, // c:285
299 MF_GAMMA => retd = unsafe { tgamma(argd) }, // c:289
300 MF_HYPOT => retd = argd.hypot(argd2), // c:300
301 MF_ILOGB => { // c:304
302 ret.type_ = MN_INTEGER;
303 ret.l = unsafe { ilogb(argd) } as i64;
304 }
305 MF_INT => { // c:309
306 ret.type_ = MN_INTEGER;
307 ret.l = argd as i64;
308 }
309 MF_ISINF => { // c:314
310 ret.type_ = MN_INTEGER;
311 ret.l = argd.is_infinite() as i64;
312 }
313 MF_ISNAN => { // c:319
314 ret.type_ = MN_INTEGER;
315 ret.l = argd.is_nan() as i64;
316 }
317 MF_J0 => retd = unsafe { j0(argd) }, // c:325
318 MF_J1 => retd = unsafe { j1(argd) }, // c:329
319 MF_JN => retd = unsafe { jn(argi, argd2) }, // c:333
320 MF_LDEXP => retd = unsafe { ldexp(argd, argi) }, // c:337
321 MF_LGAMMA => retd = unsafe { lgamma(argd) }, // c:341
322 MF_LOG => retd = argd.ln(), // c:345
323 MF_LOG10 => retd = argd.log10(), // c:349
324 MF_LOG1P => retd = unsafe { log1p(argd) }, // c:353
325 MF_LOG2 => retd = argd.log2(), // c:357
326 MF_LOGB => retd = unsafe { logb(argd) }, // c:365
327 MF_NEXTAFTER => retd = unsafe { nextafter(argd, argd2) }, // c:369
328 MF_RINT => retd = unsafe { rint(argd) }, // c:373
329 MF_SCALB => retd = unsafe { scalbn(argd, argi) }, // c:377
330 MF_SIGNGAM => { // c:386
331 ret.type_ = MN_INTEGER;
332 ret.l = 0; // signgam is libm-internal; not portably exposed.
333 }
334 MF_SIN => retd = argd.sin(), // c:392
335 MF_SINH => retd = argd.sinh(), // c:396
336 MF_SQRT => retd = argd.sqrt(), // c:400
337 MF_TAN => retd = argd.tan(), // c:404
338 MF_TANH => retd = argd.tanh(), // c:408
339 MF_Y0 => retd = unsafe { y0(argd) }, // c:412
340 MF_Y1 => retd = unsafe { y1(argd) }, // c:416
341 MF_YN => retd = unsafe { yn(argi, argd2) }, // c:420
342 _ => { // c:425
343 // BUG: mathfunc type not handled. C prints to stderr
344 // under DEBUG; production zsh silently returns 0.
345 }
346 }
347
348 if (id & tflag(TF_NOASS)) == 0 { // c:431
349 ret.d = retd; // c:432
350 }
351
352 ret // c:434
353}
354
355use crate::ported::zsh_h::features as features_t;
356use std::sync::{Mutex, OnceLock};
357
358static MODULE_FEATURES: OnceLock<Mutex<features_t>> = OnceLock::new();
359
360
361// Local stubs for the per-module entry points. C uses generic
362// `featuresarray`/`handlefeatures`/`setfeatureenables` (module.c:
363// 3275/3370/3445) but those take `Builtin` + `Features` pointer
364// fields the Rust port doesn't carry. The hardcoded descriptor
365// list mirrors the C bintab/conddefs/mathfuncs/paramdefs.
366/// Port of `math_func(UNUSED(char *name), int argc, mnumber *argv, int id)` from `Src/Modules/mathfunc.c:173`.
367fn featuresarray(_m: *const module, _f: &Mutex<features_t>) -> Vec<String> {
368 vec!["f:abs".to_string(), "f:acos".to_string(), "f:acosh".to_string(), "f:asin".to_string(), "f:asinh".to_string(), "f:atan".to_string(), "f:atanh".to_string(), "f:cbrt".to_string(), "f:ceil".to_string(), "f:copysign".to_string(), "f:cos".to_string(), "f:cosh".to_string(), "f:erf".to_string(), "f:erfc".to_string(), "f:exp".to_string(), "f:expm1".to_string(), "f:fabs".to_string(), "f:float".to_string(), "f:floor".to_string(), "f:fmod".to_string(), "f:gamma".to_string(), "f:hypot".to_string(), "f:ilogb".to_string(), "f:int".to_string(), "f:isinf".to_string(), "f:isnan".to_string(), "f:j0".to_string(), "f:j1".to_string(), "f:jn".to_string(), "f:ldexp".to_string(), "f:lgamma".to_string(), "f:log".to_string(), "f:log10".to_string(), "f:log1p".to_string(), "f:log2".to_string(), "f:logb".to_string(), "f:nextafter".to_string(), "f:rint".to_string(), "f:scalb".to_string(), "f:signgam".to_string(), "f:sin".to_string(), "f:sinh".to_string(), "f:sqrt".to_string(), "f:tan".to_string(), "f:tanh".to_string(), "f:y0".to_string(), "f:y1".to_string(), "f:yn".to_string()]
369}
370
371// WARNING: NOT IN MATHFUNC.C — Rust-only module-framework shim.
372// C uses generic featuresarray/handlefeatures/setfeatureenables from
373// Src/module.c:3275/3370/3445 with C-side Builtin/Features pointers;
374// Rust per-module shims hardcode the bintab/conddefs/mathfuncs/paramdefs.
375fn handlefeatures(
376 _m: *const module,
377 _f: &Mutex<features_t>,
378 enables: &mut Option<Vec<i32>>,
379) -> i32 {
380 if enables.is_none() {
381 *enables = Some(vec![1; 48]);
382 }
383 0
384}
385
386// WARNING: NOT IN MATHFUNC.C — Rust-only module-framework shim.
387// C uses generic featuresarray/handlefeatures/setfeatureenables from
388// Src/module.c:3275/3370/3445 with C-side Builtin/Features pointers;
389// Rust per-module shims hardcode the bintab/conddefs/mathfuncs/paramdefs.
390fn setfeatureenables(
391 _m: *const module,
392 _f: &Mutex<features_t>,
393 _e: Option<&[i32]>,
394) -> i32 {
395 0
396}
397
398// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
399// ─── RUST-ONLY ACCESSORS ───
400//
401// Singleton accessor fns for `OnceLock<Mutex<T>>` / `OnceLock<
402// RwLock<T>>` globals declared above. C zsh uses direct global
403// access; Rust needs these wrappers because `OnceLock::get_or_init`
404// is the only way to lazily construct shared state. These fns sit
405// here so the body of this file reads in C source order without
406// the accessor wrappers interleaved between real port fns.
407// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
408
409// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
410// ─── RUST-ONLY ACCESSORS ───
411//
412// Singleton accessor fns for `OnceLock<Mutex<T>>` / `OnceLock<
413// RwLock<T>>` globals declared above. C zsh uses direct global
414// access; Rust needs these wrappers because `OnceLock::get_or_init`
415// is the only way to lazily construct shared state. These fns sit
416// here so the body of this file reads in C source order without
417// the accessor wrappers interleaved between real port fns.
418// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
419
420// WARNING: NOT IN MATHFUNC.C — Rust-only module-framework shim.
421// C uses generic featuresarray/handlefeatures/setfeatureenables from
422// Src/module.c:3275/3370/3445 with C-side Builtin/Features pointers;
423// Rust per-module shims hardcode the bintab/conddefs/mathfuncs/paramdefs.
424fn module_features() -> &'static Mutex<features_t> {
425 MODULE_FEATURES.get_or_init(|| Mutex::new(features_t {
426 bn_list: None,
427 bn_size: 0,
428 cd_list: None,
429 cd_size: 0,
430 mf_list: None,
431 mf_size: 48,
432 pd_list: None,
433 pd_size: 0,
434 n_abstract: 0,
435 }))
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441
442 /// Port of `math_func(UNUSED(char *name), int argc, mnumber *argv, int id)` from `Src/Modules/mathfunc.c:173`.
443 #[test]
444 fn test_math_func_acos() {
445 let argv = [mnumber { l: 0, d: 1.0, type_: MN_FLOAT }];
446 let r = math_func("acos", 1, &argv, MF_ACOS);
447 assert!((r.type_ == MN_FLOAT));
448 assert!((r.d - 0.0).abs() < 1e-9);
449 }
450
451 /// Port of `math_func(UNUSED(char *name), int argc, mnumber *argv, int id)` from `Src/Modules/mathfunc.c:173`.
452 #[test]
453 fn test_math_func_atan_two_args() {
454 let argv = [mnumber { l: 0, d: 1.0, type_: MN_FLOAT }, mnumber { l: 0, d: 1.0, type_: MN_FLOAT }];
455 let r = math_func("atan", 2, &argv, MF_ATAN);
456 assert!((r.type_ == MN_FLOAT));
457 assert!((r.d - std::f64::consts::FRAC_PI_4).abs() < 1e-9);
458 }
459
460 /// Port of `math_func(UNUSED(char *name), int argc, mnumber *argv, int id)` from `Src/Modules/mathfunc.c:173`.
461 #[test]
462 fn test_math_func_abs_int_preserves_type() {
463 let argv = [mnumber { l: -7, d: 0.0, type_: MN_INTEGER }];
464 let r = math_func("abs", 1, &argv, MF_ABS | tflag(TF_NOCONV | TF_NOASS));
465 assert!((r.type_ == MN_INTEGER));
466 assert_eq!(r.l, 7);
467 }
468
469 /// Port of `math_func(UNUSED(char *name), int argc, mnumber *argv, int id)` from `Src/Modules/mathfunc.c:173`.
470 #[test]
471 fn test_math_func_int_truncates() {
472 let argv = [mnumber { l: 0, d: 3.7, type_: MN_FLOAT }];
473 let r = math_func("int", 1, &argv, MF_INT | tflag(TF_NOASS));
474 assert!((r.type_ == MN_INTEGER));
475 assert_eq!(r.l, 3);
476 }
477
478 /// Port of `math_func(UNUSED(char *name), int argc, mnumber *argv, int id)` from `Src/Modules/mathfunc.c:173`.
479 #[test]
480 fn test_math_func_isnan() {
481 let argv = [mnumber { l: 0, d: f64::NAN, type_: MN_FLOAT }];
482 let r = math_func("isnan", 1, &argv, MF_ISNAN | tflag(TF_NOASS));
483 assert_eq!(r.l, 1);
484 }
485
486 /// Port of `math_string(UNUSED(char *name), char *arg, int id)` from `Src/Modules/mathfunc.c:439`.
487 #[test]
488 fn test_math_string_rand48_in_range() {
489 let r = math_string("rand48", "", MS_RAND48);
490 assert!((r.type_ == MN_FLOAT));
491 assert!((0.0..1.0).contains(&r.d));
492 }
493
494 /// c:173 — `MF_COS` of 0 is 1.0 exactly. Trigonometric identity
495 /// pin; catches a regression that swaps cos/sin dispatch.
496 #[test]
497 fn math_func_cos_of_zero_is_one() {
498 let argv = [mnumber { l: 0, d: 0.0, type_: MN_FLOAT }];
499 let r = math_func("cos", 1, &argv, MF_COS);
500 assert_eq!(r.type_, MN_FLOAT);
501 assert!((r.d - 1.0).abs() < 1e-9);
502 }
503
504 /// c:173 — `MF_SIN` of 0 is 0. Symmetric to the cos test;
505 /// any libm aliasing would surface here.
506 #[test]
507 fn math_func_sin_of_zero_is_zero() {
508 let argv = [mnumber { l: 0, d: 0.0, type_: MN_FLOAT }];
509 let r = math_func("sin", 1, &argv, MF_SIN);
510 assert_eq!(r.type_, MN_FLOAT);
511 assert!(r.d.abs() < 1e-9, "sin(0) = {}", r.d);
512 }
513
514 /// c:173 — `MF_SQRT` of 4 is 2.0. Pure-math anchor that catches
515 /// any regression in the int→float promotion before sqrt.
516 #[test]
517 fn math_func_sqrt_of_four_is_two() {
518 let argv = [mnumber { l: 0, d: 4.0, type_: MN_FLOAT }];
519 let r = math_func("sqrt", 1, &argv, MF_SQRT);
520 assert_eq!(r.type_, MN_FLOAT);
521 assert!((r.d - 2.0).abs() < 1e-9, "sqrt(4) = {}", r.d);
522 }
523
524 /// c:173 — `MF_EXP` of 0 is 1.0 (e^0 identity).
525 #[test]
526 fn math_func_exp_of_zero_is_one() {
527 let argv = [mnumber { l: 0, d: 0.0, type_: MN_FLOAT }];
528 let r = math_func("exp", 1, &argv, MF_EXP);
529 assert_eq!(r.type_, MN_FLOAT);
530 assert!((r.d - 1.0).abs() < 1e-9);
531 }
532
533 /// c:173 — `MF_LOG` of 1.0 is 0.0 (natural log identity).
534 #[test]
535 fn math_func_log_of_one_is_zero() {
536 let argv = [mnumber { l: 0, d: 1.0, type_: MN_FLOAT }];
537 let r = math_func("log", 1, &argv, MF_LOG);
538 assert_eq!(r.type_, MN_FLOAT);
539 assert!(r.d.abs() < 1e-9, "log(1) = {}", r.d);
540 }
541
542 /// c:173 — `MF_FLOOR` of 3.7 is 3.0 (NOT 4.0). Pin direction
543 /// because a regen could swap floor/ceil dispatch.
544 #[test]
545 fn math_func_floor_rounds_down() {
546 let argv = [mnumber { l: 0, d: 3.7, type_: MN_FLOAT }];
547 let r = math_func("floor", 1, &argv, MF_FLOOR);
548 assert_eq!(r.type_, MN_FLOAT);
549 assert_eq!(r.d, 3.0);
550 }
551
552 /// c:173 — `MF_CEIL` of 3.1 is 4.0. Symmetric to floor.
553 #[test]
554 fn math_func_ceil_rounds_up() {
555 let argv = [mnumber { l: 0, d: 3.1, type_: MN_FLOAT }];
556 let r = math_func("ceil", 1, &argv, MF_CEIL);
557 assert_eq!(r.type_, MN_FLOAT);
558 assert_eq!(r.d, 4.0);
559 }
560
561 /// c:173 — `MF_FABS` of negative is positive AND the result
562 /// type stays MN_FLOAT (NOT coerced to MN_INTEGER like the
563 /// integer-typed `abs`).
564 #[test]
565 fn math_func_fabs_preserves_float_type() {
566 let argv = [mnumber { l: 0, d: -2.5, type_: MN_FLOAT }];
567 let r = math_func("fabs", 1, &argv, MF_FABS);
568 assert_eq!(r.type_, MN_FLOAT);
569 assert_eq!(r.d, 2.5);
570 }
571
572 /// c:173 — `MF_ISINF` of +infinity is 1; of finite is 0. Pin
573 /// both branches so a regression that returns the IEEE-754
574 /// classify code (3 / 0 / 4 / 5) instead of the boolean gets
575 /// caught.
576 #[test]
577 fn math_func_isinf_classifies_correctly() {
578 let argv_inf = [mnumber { l: 0, d: f64::INFINITY, type_: MN_FLOAT }];
579 let r_inf = math_func("isinf", 1, &argv_inf, MF_ISINF | tflag(TF_NOASS));
580 assert_eq!(r_inf.l, 1, "isinf(+inf) must be 1");
581
582 let argv_fin = [mnumber { l: 0, d: 1.5, type_: MN_FLOAT }];
583 let r_fin = math_func("isinf", 1, &argv_fin, MF_ISINF | tflag(TF_NOASS));
584 assert_eq!(r_fin.l, 0, "isinf(finite) must be 0");
585 }
586
587 /// c:439 — `math_string` for an unknown id must not panic.
588 /// Defensive contract; return value is impl-defined but the
589 /// function must not crash.
590 #[test]
591 fn math_string_unknown_id_does_not_panic() {
592 let _ = math_string("nope", "", 9999);
593 }
594
595 /// c:548-590 — module-lifecycle stubs all return 0 in C.
596 #[test]
597 fn module_lifecycle_shims_all_return_zero() {
598 let m: *const module = std::ptr::null();
599 assert_eq!(setup_(m), 0);
600 assert_eq!(boot_(m), 0);
601 assert_eq!(cleanup_(m), 0);
602 assert_eq!(finish_(m), 0);
603 }
604}