• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

source-academy / py-slang / 24697489873

21 Apr 2026 12:27AM UTC coverage: 73.8% (+0.5%) from 73.253%
24697489873

Pull #147

github

web-flow
Merge 2b15214dd into 98f152d78
Pull Request #147: Split `stdlib.ts` into a `MATH` and `MISC` group

1562 of 2383 branches covered (65.55%)

Branch coverage included in aggregate %.

273 of 341 new or added lines in 11 files covered. (80.06%)

4559 of 5911 relevant lines covered (77.13%)

11051.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

11.01
/src/stdlib/math.ts
1
import { erf, gamma, lgamma } from "mathjs";
12✔
2
import { ExprNS } from "../ast-types";
3
import { Context } from "../engines/cse/context";
4
import { handleRuntimeError } from "../engines/cse/error";
12✔
5
import { BigIntValue, BoolValue, BuiltinValue, NumberValue, Value } from "../engines/cse/stash";
6
import { isNumeric } from "../engines/cse/utils";
12✔
7
import { TypeError, ValueError } from "../errors";
12✔
8
import { GroupName, minArgMap, Validate } from "./utils";
12✔
9

10
const mathBuiltins = new Map<string, Value>();
12✔
11

12
const constantMap = {
12✔
13
  math_e: { type: "number", value: Math.E },
14
  math_inf: { type: "number", value: Infinity },
15
  math_nan: { type: "number", value: NaN },
16
  math_pi: { type: "number", value: Math.PI },
17
  math_tau: { type: "number", value: 2 * Math.PI },
18
} as const;
19

20
export class MathBuiltins {
12✔
21
  @Validate(1, 1, "math_acos", false)
22
  static math_acos(
12✔
23
    args: Value[],
24
    source: string,
25
    command: ExprNS.Call,
26
    context: Context,
27
  ): NumberValue {
28
    const x = args[0];
×
29
    if (!isNumeric(x)) {
×
30
      handleRuntimeError(
×
31
        context,
32
        new TypeError(source, command, context, x.type, "float' or 'int"),
33
      );
34
    }
35

36
    let num: number;
37
    if (x.type === "number") {
×
38
      num = x.value;
×
39
    } else {
40
      num = Number(x.value);
×
41
    }
42

43
    if (num < -1 || num > 1) {
×
44
      handleRuntimeError(context, new ValueError(source, command, context, "math_acos"));
×
45
    }
46

47
    const result = Math.acos(num);
×
48
    return { type: "number", value: result };
×
49
  }
50

51
  @Validate(1, 1, "math_acosh", false)
52
  static math_acosh(
12✔
53
    args: Value[],
54
    source: string,
55
    command: ExprNS.Call,
56
    context: Context,
57
  ): NumberValue {
58
    const x = args[0];
×
59

60
    if (!isNumeric(x)) {
×
61
      handleRuntimeError(
×
62
        context,
63
        new TypeError(source, command, context, x.type, "float' or 'int"),
64
      );
65
    }
66

67
    let num: number;
68
    if (x.type === "number") {
×
69
      num = x.value;
×
70
    } else {
71
      num = Number(x.value);
×
72
    }
73

74
    if (num < 1) {
×
75
      handleRuntimeError(context, new ValueError(source, command, context, "math_acosh"));
×
76
    }
77

78
    const result = Math.acosh(num);
×
79
    return { type: "number", value: result };
×
80
  }
81

82
  @Validate(1, 1, "math_asin", false)
83
  static math_asin(
12✔
84
    args: Value[],
85
    source: string,
86
    command: ExprNS.Call,
87
    context: Context,
88
  ): NumberValue {
89
    const x = args[0];
×
90
    if (!isNumeric(x)) {
×
91
      handleRuntimeError(
×
92
        context,
93
        new TypeError(source, command, context, x.type, "float' or 'int"),
94
      );
95
    }
96

97
    let num: number;
98
    if (x.type === "number") {
×
99
      num = x.value;
×
100
    } else {
101
      num = Number(x.value);
×
102
    }
103

104
    if (num < -1 || num > 1) {
×
105
      handleRuntimeError(context, new ValueError(source, command, context, "math_asin"));
×
106
    }
107

108
    const result = Math.asin(num);
×
109
    return { type: "number", value: result };
×
110
  }
111

112
  @Validate(1, 1, "math_asinh", false)
113
  static math_asinh(
12✔
114
    args: Value[],
115
    source: string,
116
    command: ExprNS.Call,
117
    context: Context,
118
  ): NumberValue {
119
    const x = args[0];
×
120
    if (!isNumeric(x)) {
×
121
      handleRuntimeError(
×
122
        context,
123
        new TypeError(source, command, context, x.type, "float' or 'int"),
124
      );
125
    }
126

127
    let num: number;
128
    if (x.type === "number") {
×
129
      num = x.value;
×
130
    } else {
131
      num = Number(x.value);
×
132
    }
133

134
    const result = Math.asinh(num);
×
135
    return { type: "number", value: result };
×
136
  }
137

138
  @Validate(1, 1, "math_atan", false)
139
  static math_atan(
12✔
140
    args: Value[],
141
    source: string,
142
    command: ExprNS.Call,
143
    context: Context,
144
  ): NumberValue {
145
    const x = args[0];
×
146
    if (!isNumeric(x)) {
×
147
      handleRuntimeError(
×
148
        context,
149
        new TypeError(source, command, context, x.type, "float' or 'int"),
150
      );
151
    }
152

153
    let num: number;
154
    if (x.type === "number") {
×
155
      num = x.value;
×
156
    } else {
157
      num = Number(x.value);
×
158
    }
159

160
    const result = Math.atan(num);
×
161
    return { type: "number", value: result };
×
162
  }
163

164
  @Validate(2, 2, "math_atan2", false)
165
  static math_atan2(
12✔
166
    args: Value[],
167
    source: string,
168
    command: ExprNS.Call,
169
    context: Context,
170
  ): NumberValue {
171
    const y = args[0];
×
172
    const x = args[1];
×
173
    if (!isNumeric(x)) {
×
174
      handleRuntimeError(
×
175
        context,
176
        new TypeError(source, command, context, x.type, "float' or 'int"),
177
      );
178
    } else if (!isNumeric(y)) {
×
179
      handleRuntimeError(
×
180
        context,
181
        new TypeError(source, command, context, y.type, "float' or 'int"),
182
      );
183
    }
184

185
    let yNum: number, xNum: number;
186
    if (y.type === "number") {
×
187
      yNum = y.value;
×
188
    } else {
189
      yNum = Number(y.value);
×
190
    }
191

192
    if (x.type === "number") {
×
193
      xNum = x.value;
×
194
    } else {
195
      xNum = Number(x.value);
×
196
    }
197

198
    const result = Math.atan2(yNum, xNum);
×
199
    return { type: "number", value: result };
×
200
  }
201

202
  @Validate(1, 1, "math_atanh", false)
203
  static math_atanh(
12✔
204
    args: Value[],
205
    source: string,
206
    command: ExprNS.Call,
207
    context: Context,
208
  ): NumberValue {
209
    const x = args[0];
×
210
    if (!isNumeric(x)) {
×
211
      handleRuntimeError(
×
212
        context,
213
        new TypeError(source, command, context, x.type, "float' or 'int"),
214
      );
215
    }
216

217
    let num: number;
218
    if (x.type === "number") {
×
219
      num = x.value;
×
220
    } else {
221
      num = Number(x.value);
×
222
    }
223

224
    if (num <= -1 || num >= 1) {
×
225
      handleRuntimeError(context, new ValueError(source, command, context, "math_atanh"));
×
226
    }
227

228
    const result = Math.atanh(num);
×
229
    return { type: "number", value: result };
×
230
  }
231

232
  @Validate(1, 1, "math_cos", false)
233
  static math_cos(
12✔
234
    args: Value[],
235
    source: string,
236
    command: ExprNS.Call,
237
    context: Context,
238
  ): NumberValue {
239
    const x = args[0];
7✔
240
    if (!isNumeric(x)) {
7✔
241
      handleRuntimeError(
3✔
242
        context,
243
        new TypeError(source, command, context, x.type, "float' or 'int"),
244
      );
245
    }
246

247
    let num: number;
248
    if (x.type === "number") {
4✔
249
      num = x.value;
3✔
250
    } else {
251
      num = Number(x.value);
1✔
252
    }
253

254
    const result = Math.cos(num);
4✔
255
    return { type: "number", value: result };
4✔
256
  }
257

258
  @Validate(1, 1, "math_cosh", false)
259
  static math_cosh(
12✔
260
    args: Value[],
261
    source: string,
262
    command: ExprNS.Call,
263
    context: Context,
264
  ): NumberValue {
265
    const x = args[0];
×
266
    if (!isNumeric(x)) {
×
267
      handleRuntimeError(
×
268
        context,
269
        new TypeError(source, command, context, x.type, "float' or 'int"),
270
      );
271
    }
272

273
    let num: number;
274
    if (x.type === "number") {
×
275
      num = x.value;
×
276
    } else {
277
      num = Number(x.value);
×
278
    }
279

280
    const result = Math.cosh(num);
×
281
    return { type: "number", value: result };
×
282
  }
283

284
  @Validate(1, 1, "math_degrees", false)
285
  static math_degrees(
12✔
286
    args: Value[],
287
    source: string,
288
    command: ExprNS.Call,
289
    context: Context,
290
  ): NumberValue {
291
    const x = args[0];
×
292
    if (!isNumeric(x)) {
×
293
      handleRuntimeError(
×
294
        context,
295
        new TypeError(source, command, context, x.type, "float' or 'int"),
296
      );
297
    }
298

299
    let num: number;
300
    if (x.type === "number") {
×
301
      num = x.value;
×
302
    } else {
303
      num = Number(x.value);
×
304
    }
305

306
    const result = (num * 180) / Math.PI;
×
307
    return { type: "number", value: result };
×
308
  }
309

310
  @Validate(1, 1, "math_erf", false)
311
  static math_erf(
12✔
312
    args: Value[],
313
    source: string,
314
    command: ExprNS.Call,
315
    context: Context,
316
  ): NumberValue {
317
    const x = args[0];
×
318
    if (!isNumeric(x)) {
×
319
      handleRuntimeError(
×
320
        context,
321
        new TypeError(source, command, context, x.type, "float' or 'int"),
322
      );
323
    }
324

325
    let num: number;
326
    if (x.type === "number") {
×
327
      num = x.value;
×
328
    } else {
329
      num = Number(x.value);
×
330
    }
331

332
    const erfnum = erf(num);
×
333

334
    return { type: "number", value: erfnum };
×
335
  }
336

337
  @Validate(1, 1, "math_erfc", false)
338
  static math_erfc(
12✔
339
    args: Value[],
340
    source: string,
341
    command: ExprNS.Call,
342
    context: Context,
343
  ): NumberValue {
344
    const x = args[0];
×
345
    if (!isNumeric(x)) {
×
346
      handleRuntimeError(
×
347
        context,
348
        new TypeError(source, command, context, x.type, "float' or 'int"),
349
      );
350
    }
351

NEW
352
    const erfc = 1 - MathBuiltins.math_erf([args[0]], source, command, context).value;
×
353

354
    return { type: "number", value: erfc };
×
355
  }
356

357
  @Validate(2, 2, "math_comb", false)
358
  static math_comb(
12✔
359
    args: Value[],
360
    source: string,
361
    command: ExprNS.Call,
362
    context: Context,
363
  ): BigIntValue {
364
    const n = args[0];
×
365
    const k = args[1];
×
366

367
    if (n.type !== "bigint") {
×
368
      handleRuntimeError(context, new TypeError(source, command, context, n.type, "int"));
×
369
    } else if (k.type !== "bigint") {
×
370
      handleRuntimeError(context, new TypeError(source, command, context, k.type, "int"));
×
371
    }
372

373
    const nVal = BigInt(n.value);
×
374
    const kVal = BigInt(k.value);
×
375

376
    if (nVal < 0 || kVal < 0) {
×
377
      handleRuntimeError(context, new ValueError(source, command, context, "math_comb"));
×
378
    }
379

380
    if (kVal > nVal) {
×
381
      return { type: "bigint", value: BigInt(0) };
×
382
    }
383

384
    let result: bigint = BigInt(1);
×
385
    const kk = kVal > nVal - kVal ? nVal - kVal : kVal;
×
386

387
    for (let i: bigint = BigInt(0); i < kk; i++) {
×
388
      result = (result * (nVal - i)) / (i + BigInt(1));
×
389
    }
390

391
    return { type: "bigint", value: result };
×
392
  }
393

394
  @Validate(1, 1, "math_factorial", false)
395
  static math_factorial(
12✔
396
    args: Value[],
397
    source: string,
398
    command: ExprNS.Call,
399
    context: Context,
400
  ): BigIntValue {
401
    const n = args[0];
×
402

403
    if (n.type !== "bigint") {
×
404
      handleRuntimeError(context, new TypeError(source, command, context, n.type, "int"));
×
405
    }
406

407
    const nVal = BigInt(n.value);
×
408

409
    if (nVal < 0) {
×
410
      handleRuntimeError(context, new ValueError(source, command, context, "math_factorial"));
×
411
    }
412

413
    // 0! = 1
414
    if (nVal === BigInt(0)) {
×
415
      return { type: "bigint", value: BigInt(1) };
×
416
    }
417

418
    let result: bigint = BigInt(1);
×
419
    for (let i: bigint = BigInt(1); i <= nVal; i++) {
×
420
      result *= i;
×
421
    }
422

423
    return { type: "bigint", value: result };
×
424
  }
425

426
  static math_gcd(
427
    args: Value[],
428
    source: string,
429
    command: ExprNS.Call,
430
    context: Context,
431
  ): BigIntValue {
432
    if (args.length === 0) {
×
433
      return { type: "bigint", value: BigInt(0) };
×
434
    }
435

436
    const values = args.map(v => {
×
437
      if (v.type !== "bigint") {
×
438
        handleRuntimeError(context, new TypeError(source, command, context, v.type, "int"));
×
439
      }
440
      return BigInt(v.value);
×
441
    });
442

443
    const allZero = values.every(val => val === BigInt(0));
×
444
    if (allZero) {
×
445
      return { type: "bigint", value: BigInt(0) };
×
446
    }
447

448
    let currentGcd: bigint = values[0] < 0 ? -values[0] : values[0];
×
449
    for (let i = 1; i < values.length; i++) {
×
NEW
450
      currentGcd = MathBuiltins._gcdOfTwo(currentGcd, values[i] < 0 ? -values[i] : values[i]);
×
451
      if (currentGcd === BigInt(1)) {
×
452
        break;
×
453
      }
454
    }
455

456
    return { type: "bigint", value: currentGcd };
×
457
  }
458

459
  static _gcdOfTwo(a: bigint, b: bigint): bigint {
460
    let x: bigint = a;
×
461
    let y: bigint = b;
×
462
    while (y !== BigInt(0)) {
×
463
      const temp = x % y;
×
464
      x = y;
×
465
      y = temp;
×
466
    }
467
    return x < 0 ? -x : x;
×
468
  }
469

470
  @Validate(1, 1, "math_isqrt", false)
471
  static math_isqrt(
12✔
472
    args: Value[],
473
    source: string,
474
    command: ExprNS.Call,
475
    context: Context,
476
  ): BigIntValue {
477
    const nValObj = args[0];
×
478
    if (nValObj.type !== "bigint") {
×
479
      handleRuntimeError(context, new TypeError(source, command, context, nValObj.type, "int"));
×
480
    }
481

482
    const n: bigint = nValObj.value;
×
483

484
    if (n < 0) {
×
485
      handleRuntimeError(context, new ValueError(source, command, context, "math_isqrt"));
×
486
    }
487

488
    if (n < 2) {
×
489
      return { type: "bigint", value: n };
×
490
    }
491

492
    let low: bigint = BigInt(1);
×
493
    let high: bigint = n;
×
494

495
    while (low < high) {
×
496
      const mid = (low + high + BigInt(1)) >> BigInt(1);
×
497
      const sq = mid * mid;
×
498
      if (sq <= n) {
×
499
        low = mid;
×
500
      } else {
501
        high = mid - BigInt(1);
×
502
      }
503
    }
504

505
    return { type: "bigint", value: low };
×
506
  }
507

508
  static math_lcm(
509
    args: Value[],
510
    source: string,
511
    command: ExprNS.Call,
512
    context: Context,
513
  ): BigIntValue {
514
    if (args.length === 0) {
×
515
      return { type: "bigint", value: BigInt(1) };
×
516
    }
517

518
    const values = args.map(val => {
×
519
      if (val.type !== "bigint") {
×
520
        handleRuntimeError(context, new TypeError(source, command, context, val.type, "int"));
×
521
      }
522
      return BigInt(val.value);
×
523
    });
524

525
    if (values.some(v => v === BigInt(0))) {
×
526
      return { type: "bigint", value: BigInt(0) };
×
527
    }
528

NEW
529
    let currentLcm: bigint = MathBuiltins._absBigInt(values[0]);
×
530
    for (let i = 1; i < values.length; i++) {
×
NEW
531
      currentLcm = MathBuiltins._lcmOfTwo(currentLcm, MathBuiltins._absBigInt(values[i]));
×
532
      if (currentLcm === BigInt(0)) {
×
533
        break;
×
534
      }
535
    }
536

537
    return { type: "bigint", value: currentLcm };
×
538
  }
539

540
  static _lcmOfTwo(a: bigint, b: bigint): bigint {
NEW
541
    const gcdVal: bigint = MathBuiltins._gcdOfTwo(a, b);
×
542
    return BigInt((a / gcdVal) * b);
×
543
  }
544

545
  static _absBigInt(x: bigint): bigint {
546
    return x < 0 ? -x : x;
×
547
  }
548

549
  @Validate(1, 2, "math_perm", true)
550
  static math_perm(
12✔
551
    args: Value[],
552
    source: string,
553
    command: ExprNS.Call,
554
    context: Context,
555
  ): BigIntValue {
556
    const nValObj = args[0];
×
557
    if (nValObj.type !== "bigint") {
×
558
      handleRuntimeError(context, new TypeError(source, command, context, nValObj.type, "int"));
×
559
    }
560
    const n = BigInt(nValObj.value);
×
561

562
    let k = n;
×
563
    if (args.length === 2) {
×
564
      const kValObj = args[1];
×
565
      if (kValObj.type === "none") {
×
566
        k = n;
×
567
      } else if (kValObj.type === "bigint") {
×
568
        k = BigInt(kValObj.value);
×
569
      } else {
570
        handleRuntimeError(
×
571
          context,
572
          new TypeError(source, command, context, kValObj.type, "int' or 'None"),
573
        );
574
      }
575
    }
576

577
    if (n < 0 || k < 0) {
×
578
      handleRuntimeError(context, new ValueError(source, command, context, "math_perm"));
×
579
    }
580

581
    if (k > n) {
×
582
      return { type: "bigint", value: BigInt(0) };
×
583
    }
584

585
    let result: bigint = BigInt(1);
×
586
    for (let i: bigint = BigInt(0); i < k; i++) {
×
587
      result *= n - i;
×
588
    }
589

590
    return { type: "bigint", value: result };
×
591
  }
592

593
  @Validate(1, 1, "math_ceil", false)
594
  static math_ceil(
12✔
595
    args: Value[],
596
    source: string,
597
    command: ExprNS.Call,
598
    context: Context,
599
  ): BigIntValue {
600
    const x = args[0];
×
601

602
    if (x.type === "bigint") {
×
603
      return x;
×
604
    }
605

606
    if (x.type === "number") {
×
607
      const numVal = x.value;
×
608
      const ceiled: bigint = BigInt(Math.ceil(numVal));
×
609
      return { type: "bigint", value: ceiled };
×
610
    }
611

612
    handleRuntimeError(context, new TypeError(source, command, context, x.type, "float' or 'int"));
×
613
  }
614

615
  @Validate(1, 1, "math_fabs", false)
616
  static math_fabs(
12✔
617
    args: Value[],
618
    source: string,
619
    command: ExprNS.Call,
620
    context: Context,
621
  ): NumberValue {
622
    const x = args[0];
×
623

624
    if (x.type === "bigint") {
×
625
      const bigVal: bigint = BigInt(x.value);
×
626
      const absVal: number = bigVal < 0 ? -Number(bigVal) : Number(bigVal);
×
627
      return { type: "number", value: absVal };
×
628
    }
629

630
    if (x.type === "number") {
×
631
      const numVal: number = x.value;
×
632
      if (typeof numVal !== "number") {
×
633
        handleRuntimeError(
×
634
          context,
635
          new TypeError(source, command, context, x.type, "float' or 'int"),
636
        );
637
      }
638
      const absVal: number = Math.abs(numVal);
×
639
      return { type: "number", value: absVal };
×
640
    }
641

642
    handleRuntimeError(context, new TypeError(source, command, context, x.type, "float' or 'int"));
×
643
  }
644

645
  @Validate(1, 1, "math_floor", false)
646
  static math_floor(
12✔
647
    args: Value[],
648
    source: string,
649
    command: ExprNS.Call,
650
    context: Context,
651
  ): BigIntValue {
652
    const x = args[0];
×
653

654
    if (x.type === "bigint") {
×
655
      return x;
×
656
    }
657

658
    if (x.type === "number") {
×
659
      const numVal: number = x.value;
×
660
      if (typeof numVal !== "number") {
×
661
        handleRuntimeError(
×
662
          context,
663
          new TypeError(source, command, context, x.type, "float' or 'int"),
664
        );
665
      }
666
      const floored: bigint = BigInt(Math.floor(numVal));
×
667
      return { type: "bigint", value: floored };
×
668
    }
669

670
    handleRuntimeError(context, new TypeError(source, command, context, x.type, "float' or 'int"));
×
671
  }
672

673
  // Computes the product of a and b along with the rounding error using Dekker's algorithm.
674
  static _twoProd(a: number, b: number): { prod: number; err: number } {
675
    const prod = a * b;
×
676
    const c = 134217729; // 2^27 + 1
×
677
    const a_hi = a * c - (a * c - a);
×
678
    const a_lo = a - a_hi;
×
679
    const b_hi = b * c - (b * c - b);
×
680
    const b_lo = b - b_hi;
×
681
    const err = a_lo * b_lo - (prod - a_hi * b_hi - a_lo * b_hi - a_hi * b_lo);
×
682
    return { prod, err };
×
683
  }
684

685
  // Computes the sum of a and b along with the rounding error using Fast TwoSum.
686
  static _twoSum(a: number, b: number): { sum: number; err: number } {
687
    const sum = a + b;
×
688
    const v = sum - a;
×
689
    const err = a - (sum - v) + (b - v);
×
690
    return { sum, err };
×
691
  }
692

693
  // Performs a fused multiply-add operation: computes (x * y) + z with a single rounding.
694
  static _fusedMultiplyAdd(x: number, y: number, z: number): number {
NEW
695
    const { prod, err: prodErr } = MathBuiltins._twoProd(x, y);
×
NEW
696
    const { sum, err: sumErr } = MathBuiltins._twoSum(prod, z);
×
697
    const result = sum + (prodErr + sumErr);
×
698
    return result;
×
699
  }
700

701
  static _toNumber(val: Value, source: string, command: ExprNS.Call, context: Context): number {
702
    if (val.type === "bigint") {
×
703
      return Number(val.value);
×
704
    } else if (val.type === "number") {
×
705
      return val.value;
×
706
    } else {
707
      handleRuntimeError(
×
708
        context,
709
        new TypeError(source, command, context, val.type, "float' or 'int"),
710
      );
711
    }
712
  }
713

714
  @Validate(3, 3, "math_fma", false)
715
  static math_fma(
12✔
716
    args: Value[],
717
    source: string,
718
    command: ExprNS.Call,
719
    context: Context,
720
  ): NumberValue {
NEW
721
    const xVal = MathBuiltins._toNumber(args[0], source, command, context);
×
NEW
722
    const yVal = MathBuiltins._toNumber(args[1], source, command, context);
×
NEW
723
    const zVal = MathBuiltins._toNumber(args[2], source, command, context);
×
724

725
    // Special-case handling: According to the IEEE 754 standard, fma(0, inf, nan)
726
    // and fma(inf, 0, nan) should return NaN.
727
    if (isNaN(xVal) || isNaN(yVal) || isNaN(zVal)) {
×
728
      return { type: "number", value: NaN };
×
729
    }
730
    if (xVal === 0 && !isFinite(yVal) && isNaN(zVal)) {
×
731
      return { type: "number", value: NaN };
×
732
    }
733
    if (yVal === 0 && !isFinite(xVal) && isNaN(zVal)) {
×
734
      return { type: "number", value: NaN };
×
735
    }
736

NEW
737
    const result = MathBuiltins._fusedMultiplyAdd(xVal, yVal, zVal);
×
738
    return { type: "number", value: result };
×
739
  }
740

741
  @Validate(2, 2, "math_fmod", false)
742
  static math_fmod(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
12✔
743
    // Convert inputs to numbers
NEW
744
    const xVal = MathBuiltins._toNumber(args[0], source, command, context);
×
NEW
745
    const yVal = MathBuiltins._toNumber(args[1], source, command, context);
×
746

747
    // Divisor cannot be zero
748
    if (yVal === 0) {
×
749
      handleRuntimeError(context, new ValueError(source, command, context, "math_fmod"));
×
750
    }
751

752
    // JavaScript's % operator behaves similarly to C's fmod
753
    // in that the sign of the result is the same as the sign of x.
754
    // For corner cases (NaN, Infinity), JavaScript remainder
755
    // yields results consistent with typical C library fmod behavior.
756
    const remainder = xVal % yVal;
×
757

758
    return { type: "number", value: remainder };
×
759
  }
760

761
  static _roundToEven(num: number): number {
762
    //uses Banker's Rounding as per Python's Round() function
763
    const floorVal = Math.floor(num);
×
764
    const ceilVal = Math.ceil(num);
×
765
    const diffFloor = num - floorVal;
×
766
    const diffCeil = ceilVal - num;
×
767
    if (diffFloor < diffCeil) {
×
768
      return floorVal;
×
769
    } else if (diffCeil < diffFloor) {
×
770
      return ceilVal;
×
771
    } else {
772
      return floorVal % 2 === 0 ? floorVal : ceilVal;
×
773
    }
774
  }
775

776
  @Validate(2, 2, "math_remainder", false)
777
  static math_remainder(
12✔
778
    args: Value[],
779
    source: string,
780
    command: ExprNS.Call,
781
    context: Context,
782
  ): NumberValue {
783
    const x = args[0];
×
784
    const y = args[1];
×
785

786
    let xValue: number;
787
    if (x.type === "bigint") {
×
788
      xValue = Number(x.value);
×
789
    } else if (x.type === "number") {
×
790
      xValue = x.value;
×
791
    } else {
792
      handleRuntimeError(
×
793
        context,
794
        new TypeError(source, command, context, x.type, "float' or 'int"),
795
      );
796
    }
797

798
    let yValue: number;
799
    if (y.type === "bigint") {
×
800
      yValue = Number(y.value);
×
801
    } else if (y.type === "number") {
×
802
      yValue = y.value;
×
803
    } else {
804
      handleRuntimeError(
×
805
        context,
806
        new TypeError(source, command, context, y.type, "float' or 'int"),
807
      );
808
    }
809

810
    if (yValue === 0) {
×
811
      handleRuntimeError(context, new ValueError(source, command, context, "math_remainder"));
×
812
    }
813

814
    const quotient = xValue / yValue;
×
NEW
815
    const n = MathBuiltins._roundToEven(quotient);
×
816
    const remainder = xValue - n * yValue;
×
817

818
    return { type: "number", value: remainder };
×
819
  }
820

821
  @Validate(1, 1, "math_trunc", false)
822
  static math_trunc(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
12✔
823
    const x = args[0];
×
824

825
    if (x.type === "bigint") {
×
826
      return x;
×
827
    }
828

829
    if (x.type === "number") {
×
830
      const numVal: number = x.value;
×
831
      if (typeof numVal !== "number") {
×
832
        handleRuntimeError(
×
833
          context,
834
          new TypeError(source, command, context, x.type, "float' or 'int"),
835
        );
836
      }
837
      let truncated: number;
838
      if (numVal === 0) {
×
839
        truncated = 0;
×
840
      } else if (numVal < 0) {
×
841
        truncated = Math.ceil(numVal);
×
842
      } else {
843
        truncated = Math.floor(numVal);
×
844
      }
845
      return { type: "bigint", value: BigInt(truncated) };
×
846
    }
847

848
    handleRuntimeError(context, new TypeError(source, command, context, x.type, "float' or 'int"));
×
849
  }
850

851
  @Validate(2, 2, "math_copysign", false)
852
  static math_copysign(
12✔
853
    args: Value[],
854
    source: string,
855
    command: ExprNS.Call,
856
    context: Context,
857
  ): NumberValue {
858
    const [x, y] = args;
×
859

860
    if (!isNumeric(x)) {
×
861
      handleRuntimeError(
×
862
        context,
863
        new TypeError(source, command, context, x.type, "float' or 'int"),
864
      );
865
    } else if (!isNumeric(y)) {
×
866
      handleRuntimeError(
×
867
        context,
868
        new TypeError(source, command, context, y.type, "float' or 'int"),
869
      );
870
    }
871

872
    const xVal = Number(x.value);
×
873
    const yVal = Number(y.value);
×
874

875
    const absVal = Math.abs(xVal);
×
876
    const isNegative = yVal < 0 || Object.is(yVal, -0);
×
877
    const result = isNegative ? -absVal : absVal;
×
878

879
    return { type: "number", value: Number(result) };
×
880
  }
881

882
  @Validate(1, 1, "math_isfinite", false)
883
  static math_isfinite(
12✔
884
    args: Value[],
885
    source: string,
886
    command: ExprNS.Call,
887
    context: Context,
888
  ): BoolValue {
889
    const xValObj = args[0];
×
890
    if (!isNumeric(xValObj)) {
×
891
      handleRuntimeError(
×
892
        context,
893
        new TypeError(source, command, context, xValObj.type, "float' or 'int"),
894
      );
895
    }
896

897
    const x = Number(xValObj.value);
×
898
    const result: boolean = Number.isFinite(x);
×
899

900
    return { type: "bool", value: result };
×
901
  }
902

903
  @Validate(1, 1, "math_isinf", false)
904
  static math_isinf(
12✔
905
    args: Value[],
906
    source: string,
907
    command: ExprNS.Call,
908
    context: Context,
909
  ): BoolValue {
910
    const xValObj = args[0];
×
911
    if (!isNumeric(xValObj)) {
×
912
      handleRuntimeError(
×
913
        context,
914
        new TypeError(source, command, context, xValObj.type, "float' or 'int"),
915
      );
916
    }
917

918
    const x = Number(xValObj.value);
×
919
    const result: boolean = x === Infinity || x === -Infinity;
×
920

921
    return { type: "bool", value: result };
×
922
  }
923

924
  @Validate(1, 1, "math_isnan", false)
925
  static math_isnan(
12✔
926
    args: Value[],
927
    source: string,
928
    command: ExprNS.Call,
929
    context: Context,
930
  ): BoolValue {
931
    const xValObj = args[0];
×
932
    if (!isNumeric(xValObj)) {
×
933
      handleRuntimeError(
×
934
        context,
935
        new TypeError(source, command, context, xValObj.type, "float' or 'int"),
936
      );
937
    }
938

939
    const x = Number(xValObj.value);
×
940
    const result: boolean = Number.isNaN(x);
×
941

942
    return { type: "bool", value: result };
×
943
  }
944

945
  @Validate(2, 2, "math_ldexp", false)
946
  static math_ldexp(
12✔
947
    args: Value[],
948
    source: string,
949
    command: ExprNS.Call,
950
    context: Context,
951
  ): NumberValue {
NEW
952
    const xVal = MathBuiltins._toNumber(args[0], source, command, context);
×
953

954
    if (args[1].type !== "bigint") {
×
955
      handleRuntimeError(context, new TypeError(source, command, context, args[1].type, "int"));
×
956
    }
957
    const expVal = args[1].value;
×
958

959
    // Perform x * 2^expVal
960
    // In JavaScript, 2**expVal may overflow or underflow, yielding Infinity or 0 respectively.
961
    // That behavior parallels typical C library rules for ldexp.
962
    const result = xVal * Math.pow(2, Number(expVal));
×
963

964
    return { type: "number", value: result };
×
965
  }
966

967
  @Validate(2, 2, "math_nextafter", false)
968
  static math_nextafter(
12✔
969
    _args: Value[],
970
    _source: string,
971
    _command: ExprNS.Call,
972
    _context: Context,
973
  ): Value {
974
    // TODO: Implement math_nextafter using proper bit-level manipulation and handling special cases (NaN, Infinity, steps, etc.)
975
    throw new Error("math_nextafter not implemented");
×
976
  }
977

978
  @Validate(1, 1, "math_ulp", false)
979
  static math_ulp(
12✔
980
    _args: Value[],
981
    _source: string,
982
    _command: ExprNS.Call,
983
    _context: Context,
984
  ): Value {
985
    // TODO: Implement math_ulp to return the unit in the last place (ULP) of the given floating-point number.
986
    throw new Error("math_ulp not implemented");
×
987
  }
988

989
  @Validate(1, 1, "math_cbrt", false)
990
  static math_cbrt(
12✔
991
    args: Value[],
992
    source: string,
993
    command: ExprNS.Call,
994
    context: Context,
995
  ): NumberValue {
996
    const xVal = args[0];
×
997
    let x: number;
998

999
    if (xVal.type !== "number") {
×
1000
      if (xVal.type === "bigint") {
×
1001
        x = Number(xVal.value);
×
1002
      } else {
1003
        handleRuntimeError(
×
1004
          context,
1005
          new TypeError(source, command, context, xVal.type, "float' or 'int"),
1006
        );
1007
      }
1008
    } else {
1009
      x = xVal.value;
×
1010
    }
1011

1012
    const result = Math.cbrt(x);
×
1013

1014
    return { type: "number", value: result };
×
1015
  }
1016

1017
  @Validate(1, 1, "math_exp", false)
1018
  static math_exp(
12✔
1019
    args: Value[],
1020
    source: string,
1021
    command: ExprNS.Call,
1022
    context: Context,
1023
  ): NumberValue {
1024
    const xVal = args[0];
×
1025
    let x: number;
1026

1027
    if (xVal.type !== "number") {
×
1028
      if (xVal.type === "bigint") {
×
1029
        x = Number(xVal.value);
×
1030
      } else {
1031
        handleRuntimeError(
×
1032
          context,
1033
          new TypeError(source, command, context, xVal.type, "float' or 'int"),
1034
        );
1035
      }
1036
    } else {
1037
      x = xVal.value;
×
1038
    }
1039

1040
    const result = Math.exp(x);
×
1041
    return { type: "number", value: result };
×
1042
  }
1043

1044
  @Validate(1, 1, "math_exp2", false)
1045
  static math_exp2(
12✔
1046
    args: Value[],
1047
    source: string,
1048
    command: ExprNS.Call,
1049
    context: Context,
1050
  ): NumberValue {
1051
    const xVal = args[0];
×
1052
    let x: number;
1053

1054
    if (xVal.type !== "number") {
×
1055
      if (xVal.type === "bigint") {
×
1056
        x = Number(xVal.value);
×
1057
      } else {
1058
        handleRuntimeError(
×
1059
          context,
1060
          new TypeError(source, command, context, xVal.type, "float' or 'int"),
1061
        );
1062
      }
1063
    } else {
1064
      x = xVal.value;
×
1065
    }
1066

1067
    const result = Math.pow(2, x);
×
1068
    return { type: "number", value: result };
×
1069
  }
1070

1071
  @Validate(1, 1, "math_expm1", false)
1072
  static math_expm1(
12✔
1073
    args: Value[],
1074
    source: string,
1075
    command: ExprNS.Call,
1076
    context: Context,
1077
  ): NumberValue {
1078
    const x = args[0];
×
1079
    if (!isNumeric(x)) {
×
1080
      handleRuntimeError(
×
1081
        context,
1082
        new TypeError(source, command, context, x.type, "float' or 'int"),
1083
      );
1084
    }
1085

1086
    let num: number;
1087
    if (x.type === "number") {
×
1088
      num = x.value;
×
1089
    } else {
1090
      num = Number(x.value);
×
1091
    }
1092

1093
    const result = Math.expm1(num);
×
1094
    return { type: "number", value: result };
×
1095
  }
1096

1097
  @Validate(1, 1, "math_gamma", false)
1098
  static math_gamma(
12✔
1099
    args: Value[],
1100
    source: string,
1101
    command: ExprNS.Call,
1102
    context: Context,
1103
  ): NumberValue {
1104
    const x = args[0];
×
1105
    if (!isNumeric(x)) {
×
1106
      handleRuntimeError(
×
1107
        context,
1108
        new TypeError(source, command, context, x.type, "float' or 'int"),
1109
      );
1110
    }
1111

NEW
1112
    const z = MathBuiltins._toNumber(x, source, command, context);
×
1113
    const result = gamma(z);
×
1114

1115
    return { type: "number", value: result };
×
1116
  }
1117

1118
  @Validate(1, 1, "math_lgamma", false)
1119
  static math_lgamma(
12✔
1120
    args: Value[],
1121
    source: string,
1122
    command: ExprNS.Call,
1123
    context: Context,
1124
  ): NumberValue {
1125
    const x = args[0];
×
1126
    if (!isNumeric(x)) {
×
1127
      handleRuntimeError(
×
1128
        context,
1129
        new TypeError(source, command, context, x.type, "float' or 'int"),
1130
      );
1131
    }
1132

NEW
1133
    const z = MathBuiltins._toNumber(x, source, command, context);
×
1134
    const result = lgamma(z);
×
1135

1136
    return { type: "number", value: result };
×
1137
  }
1138

1139
  @Validate(1, 2, "math_log", true)
1140
  static math_log(
12✔
1141
    args: Value[],
1142
    source: string,
1143
    command: ExprNS.Call,
1144
    context: Context,
1145
  ): NumberValue {
1146
    const x = args[0];
×
1147
    if (!isNumeric(x)) {
×
1148
      handleRuntimeError(
×
1149
        context,
1150
        new TypeError(source, command, context, x.type, "float' or 'int"),
1151
      );
1152
    }
1153
    let num: number;
1154
    if (x.type === "number") {
×
1155
      num = x.value;
×
1156
    } else {
1157
      num = Number(x.value);
×
1158
    }
1159

1160
    if (num <= 0) {
×
1161
      handleRuntimeError(context, new ValueError(source, command, context, "math_log"));
×
1162
    }
1163

1164
    if (args.length === 1) {
×
1165
      return { type: "number", value: Math.log(num) };
×
1166
    }
1167

1168
    const baseArg = args[1];
×
1169
    if (!isNumeric(baseArg)) {
×
1170
      handleRuntimeError(
×
1171
        context,
1172
        new TypeError(source, command, context, baseArg.type, "float' or 'int"),
1173
      );
1174
    }
1175
    let baseNum: number;
1176
    if (baseArg.type === "number") {
×
1177
      baseNum = baseArg.value;
×
1178
    } else {
1179
      baseNum = Number(baseArg.value);
×
1180
    }
1181
    if (baseNum <= 0) {
×
1182
      handleRuntimeError(context, new ValueError(source, command, context, "math_log"));
×
1183
    }
1184

1185
    const result = Math.log(num) / Math.log(baseNum);
×
1186
    return { type: "number", value: result };
×
1187
  }
1188

1189
  @Validate(1, 1, "math_log10", false)
1190
  static math_log10(
12✔
1191
    args: Value[],
1192
    source: string,
1193
    command: ExprNS.Call,
1194
    context: Context,
1195
  ): NumberValue {
1196
    const x = args[0];
×
1197
    if (!isNumeric(x)) {
×
1198
      handleRuntimeError(
×
1199
        context,
1200
        new TypeError(source, command, context, args[0].type, "float' or 'int"),
1201
      );
1202
    }
1203
    let num: number;
1204
    if (x.type === "number") {
×
1205
      num = x.value;
×
1206
    } else {
1207
      num = Number(x.value);
×
1208
    }
1209
    if (num <= 0) {
×
1210
      handleRuntimeError(context, new ValueError(source, command, context, "math_log10"));
×
1211
    }
1212

1213
    const result = Math.log10(num);
×
1214
    return { type: "number", value: result };
×
1215
  }
1216

1217
  @Validate(1, 1, "math_log1p", false)
1218
  static math_log1p(
12✔
1219
    args: Value[],
1220
    source: string,
1221
    command: ExprNS.Call,
1222
    context: Context,
1223
  ): NumberValue {
1224
    const x = args[0];
×
1225
    if (!isNumeric(x)) {
×
1226
      handleRuntimeError(
×
1227
        context,
1228
        new TypeError(source, command, context, args[0].type, "float' or 'int"),
1229
      );
1230
    }
1231
    let num: number;
1232
    if (x.type === "number") {
×
1233
      num = x.value;
×
1234
    } else {
1235
      num = Number(x.value);
×
1236
    }
1237
    if (1 + num <= 0) {
×
1238
      handleRuntimeError(context, new ValueError(source, command, context, "math_log1p"));
×
1239
    }
1240

1241
    const result = Math.log1p(num);
×
1242
    return { type: "number", value: result };
×
1243
  }
1244

1245
  @Validate(1, 1, "math_log2", false)
1246
  static math_log2(
12✔
1247
    args: Value[],
1248
    source: string,
1249
    command: ExprNS.Call,
1250
    context: Context,
1251
  ): NumberValue {
1252
    const x = args[0];
×
1253
    if (!isNumeric(x)) {
×
1254
      handleRuntimeError(
×
1255
        context,
1256
        new TypeError(source, command, context, args[0].type, "float' or 'int"),
1257
      );
1258
    }
1259
    let num: number;
1260
    if (x.type === "number") {
×
1261
      num = x.value;
×
1262
    } else {
1263
      num = Number(x.value);
×
1264
    }
1265
    if (num <= 0) {
×
1266
      handleRuntimeError(context, new ValueError(source, command, context, "math_log2"));
×
1267
    }
1268

1269
    const result = Math.log2(num);
×
1270
    return { type: "number", value: result };
×
1271
  }
1272

1273
  @Validate(2, 2, "math_pow", false)
1274
  static math_pow(
12✔
1275
    args: Value[],
1276
    source: string,
1277
    command: ExprNS.Call,
1278
    context: Context,
1279
  ): NumberValue {
1280
    const base = args[0];
×
1281
    const exp = args[1];
×
1282

1283
    if (!isNumeric(base)) {
×
1284
      handleRuntimeError(
×
1285
        context,
1286
        new TypeError(source, command, context, base.type, "float' or 'int"),
1287
      );
1288
    } else if (!isNumeric(exp)) {
×
1289
      handleRuntimeError(
×
1290
        context,
1291
        new TypeError(source, command, context, exp.type, "float' or 'int"),
1292
      );
1293
    }
1294

1295
    let baseNum: number;
1296
    if (base.type === "number") {
×
1297
      baseNum = base.value;
×
1298
    } else {
1299
      baseNum = Number(base.value);
×
1300
    }
1301

1302
    let expNum: number;
1303
    if (exp.type === "number") {
×
1304
      expNum = exp.value;
×
1305
    } else {
1306
      expNum = Number(exp.value);
×
1307
    }
1308

1309
    const result = Math.pow(baseNum, expNum);
×
1310
    return { type: "number", value: result };
×
1311
  }
1312

1313
  @Validate(1, 1, "math_radians", false)
1314
  static math_radians(
12✔
1315
    args: Value[],
1316
    source: string,
1317
    command: ExprNS.Call,
1318
    context: Context,
1319
  ): NumberValue {
1320
    const x = args[0];
×
1321
    if (!isNumeric(x)) {
×
1322
      handleRuntimeError(
×
1323
        context,
1324
        new TypeError(source, command, context, x.type, "float' or 'int"),
1325
      );
1326
    }
1327

1328
    let deg: number;
1329
    if (x.type === "number") {
×
1330
      deg = x.value;
×
1331
    } else {
1332
      deg = Number(x.value);
×
1333
    }
1334

1335
    const radians = (deg * Math.PI) / 180;
×
1336
    return { type: "number", value: radians };
×
1337
  }
1338

1339
  @Validate(1, 1, "math_sin", false)
1340
  static math_sin(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
12✔
1341
    const x = args[0];
7✔
1342
    if (!isNumeric(x)) {
7✔
1343
      handleRuntimeError(
3✔
1344
        context,
1345
        new TypeError(source, command, context, x.type, "float' or 'int"),
1346
      );
1347
    }
1348

1349
    let num: number;
1350
    if (x.type === "number") {
4✔
1351
      num = x.value;
3✔
1352
    } else {
1353
      num = Number(x.value);
1✔
1354
    }
1355

1356
    const result = Math.sin(num);
4✔
1357
    return { type: "number", value: result };
4✔
1358
  }
1359

1360
  @Validate(1, 1, "math_sinh", false)
1361
  static math_sinh(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
12✔
1362
    const x = args[0];
×
1363
    if (!isNumeric(x)) {
×
1364
      handleRuntimeError(
×
1365
        context,
1366
        new TypeError(source, command, context, x.type, "float' or 'int"),
1367
      );
1368
    }
1369

1370
    let num: number;
1371
    if (x.type === "number") {
×
1372
      num = x.value;
×
1373
    } else {
1374
      num = Number(x.value);
×
1375
    }
1376

1377
    const result = Math.sinh(num);
×
1378
    return { type: "number", value: result };
×
1379
  }
1380

1381
  @Validate(1, 1, "math_tan", false)
1382
  static math_tan(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
12✔
1383
    const x = args[0];
×
1384
    if (!isNumeric(x)) {
×
1385
      handleRuntimeError(
×
1386
        context,
1387
        new TypeError(source, command, context, x.type, "float' or 'int"),
1388
      );
1389
    }
1390

1391
    let num: number;
1392
    if (x.type === "number") {
×
1393
      num = x.value;
×
1394
    } else {
1395
      num = Number(x.value);
×
1396
    }
1397

1398
    const result = Math.tan(num);
×
1399
    return { type: "number", value: result };
×
1400
  }
1401

1402
  @Validate(1, 1, "math_tanh", false)
1403
  static math_tanh(
12✔
1404
    args: Value[],
1405
    source: string,
1406
    command: ExprNS.Call,
1407
    context: Context,
1408
  ): NumberValue {
1409
    const x = args[0];
×
1410
    if (!isNumeric(x)) {
×
1411
      handleRuntimeError(
×
1412
        context,
1413
        new TypeError(source, command, context, x.type, "float' or 'int"),
1414
      );
1415
    }
1416

1417
    let num: number;
1418
    if (x.type === "number") {
×
1419
      num = x.value;
×
1420
    } else {
1421
      num = Number(x.value);
×
1422
    }
1423

1424
    const result = Math.tanh(num);
×
1425
    return { type: "number", value: result };
×
1426
  }
1427

1428
  @Validate(1, 1, "math_sqrt", false)
1429
  static math_sqrt(
12✔
1430
    args: Value[],
1431
    source: string,
1432
    command: ExprNS.Call,
1433
    context: Context,
1434
  ): NumberValue {
1435
    const x = args[0];
×
1436
    if (!isNumeric(x)) {
×
1437
      handleRuntimeError(
×
1438
        context,
1439
        new TypeError(source, command, context, x.type, "float' or 'int"),
1440
      );
1441
    }
1442

1443
    let num: number;
1444
    if (x.type === "number") {
×
1445
      num = x.value;
×
1446
    } else {
1447
      num = Number(x.value);
×
1448
    }
1449

1450
    if (num < 0) {
×
1451
      handleRuntimeError(context, new ValueError(source, command, context, "math_sqrt"));
×
1452
    }
1453

1454
    const result = Math.sqrt(num);
×
1455
    return { type: "number", value: result };
×
1456
  }
1457
}
1458

1459
for (const builtin of Object.getOwnPropertyNames(MathBuiltins)) {
12✔
1460
  if (
720✔
1461
    typeof MathBuiltins[builtin as keyof typeof MathBuiltins] === "function" &&
1,404✔
1462
    !builtin.startsWith("_")
1463
  ) {
1464
    mathBuiltins.set(builtin, {
588✔
1465
      type: "builtin",
1466
      func: MathBuiltins[builtin as keyof typeof MathBuiltins] as BuiltinValue["func"],
1467
      name: builtin,
1468
      minArgs: minArgMap.get(builtin) || 0,
612✔
1469
    });
1470
  }
1471
}
1472
for (const [name, info] of Object.entries(constantMap)) {
12✔
1473
  mathBuiltins.set(name, info);
60✔
1474
}
1475

1476
export default {
12✔
1477
  name: GroupName.MATH,
1478
  prelude: "",
1479
  builtins: mathBuiltins,
1480
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc