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

source-academy / py-slang / 29631471367

18 Jul 2026 04:59AM UTC coverage: 80.964% (+0.3%) from 80.647%
29631471367

push

github

web-flow
Overhaul of "wrong type" messages: operators and builtin functions, CSE machine and PVML-in-browser (#273)

* Fix operand-type names leaking JS internals into TypeError messages

Both engines' "unsupported operand type(s)" errors were showing
internal representation names instead of Python type names for
several types -- reported for PVML (`1 + None` said "'null' and
'bigint'" instead of "'NoneType' and 'int'"), but the same class of
bug turned out to affect CSE too.

PVML (src/engines/pvml/types.ts): `PVMLType`'s string values were the
engine's own internal tags (JS `typeof`/`null`/discriminant strings),
used directly in error-message interpolation. Corrected every member
to the name real Python (and the CSE machine's own `typeTranslator`)
would show: NUMBER "number"->"float", BIGINT "bigint"->"int", BOOLEAN
"boolean"->"bool", STRING "string"->"str", NULL "null"->"NoneType",
ARRAY "array"->"list", CLOSURE "closure"->"function", PRIMITIVE
"primitive"->"builtin_function_or_method". COMPLEX was already
correct. UNDEFINED/ITERATOR are internal-only sentinels that can't
reach one of these messages (verified: any local/free-variable read
that would yield JS `undefined` is intercepted by
UnboundLocalError/FreeVariableUnboundError first), left as-is.

CSE (src/engines/cse/types.ts, src/errors/errors.ts): tracing the
equivalent PVML fix's own reference point -- CSE's `typeTranslator` --
surfaced two real, independent bugs of the same kind:
- `typeTranslator`'s switch had no `"list"` case, so `[1,2] + 1`
  reported "'unknown' and 'int'" instead of "'list' and 'int'".
- `src/errors/errors.ts` carried its own separate copy of
  `typeTranslator` (comment: "Local copy to avoid circular import from
  utils"), missing both the `"list"` case above and the `"builtin"`
  case the canonical copy already had -- and the circular-import
  concern the comment cites no longer applies (this file already
  imports `operatorTranslator` from the exact same module). Deleted
  the... (continued)

3423 of 4524 branches covered (75.66%)

Branch coverage included in aggregate %.

173 of 241 new or added lines in 16 files covered. (71.78%)

5 existing lines in 2 files now uncovered.

7508 of 8977 relevant lines covered (83.64%)

80734.93 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";
24✔
2
import { ExprNS } from "../ast-types";
3
import { Context } from "../engines/cse/context";
4
import { handleRuntimeError } from "../engines/cse/error";
24✔
5
import { BigIntValue, BoolValue, BuiltinValue, NumberValue, Value } from "../engines/cse/stash";
6
import { isNumeric } from "../engines/cse/utils";
24✔
7
import { TypeError, ValueError } from "../errors";
24✔
8
import { GroupName, minArgMap, Validate } from "./utils";
24✔
9

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

12
const constantMap = {
24✔
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 {
24✔
21
  @Validate(1, 1, "math_acos", false)
22
  static math_acos(
24✔
23
    args: Value[],
24
    source: string,
25
    command: ExprNS.Call,
26
    context: Context,
27
  ): NumberValue {
28
    const x = args[0];
×
29
    if (!isNumeric(x)) {
×
NEW
30
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
31
    }
32

33
    let num: number;
34
    if (x.type === "number") {
×
35
      num = x.value;
×
36
    } else {
37
      num = Number(x.value);
×
38
    }
39

40
    if (num < -1 || num > 1) {
×
41
      handleRuntimeError(context, new ValueError(source, command, context, "math_acos"));
×
42
    }
43

44
    const result = Math.acos(num);
×
45
    return { type: "number", value: result };
×
46
  }
47

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

57
    if (!isNumeric(x)) {
×
NEW
58
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
59
    }
60

61
    let num: number;
62
    if (x.type === "number") {
×
63
      num = x.value;
×
64
    } else {
65
      num = Number(x.value);
×
66
    }
67

68
    if (num < 1) {
×
69
      handleRuntimeError(context, new ValueError(source, command, context, "math_acosh"));
×
70
    }
71

72
    const result = Math.acosh(num);
×
73
    return { type: "number", value: result };
×
74
  }
75

76
  @Validate(1, 1, "math_asin", false)
77
  static math_asin(
24✔
78
    args: Value[],
79
    source: string,
80
    command: ExprNS.Call,
81
    context: Context,
82
  ): NumberValue {
83
    const x = args[0];
×
84
    if (!isNumeric(x)) {
×
NEW
85
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
86
    }
87

88
    let num: number;
89
    if (x.type === "number") {
×
90
      num = x.value;
×
91
    } else {
92
      num = Number(x.value);
×
93
    }
94

95
    if (num < -1 || num > 1) {
×
96
      handleRuntimeError(context, new ValueError(source, command, context, "math_asin"));
×
97
    }
98

99
    const result = Math.asin(num);
×
100
    return { type: "number", value: result };
×
101
  }
102

103
  @Validate(1, 1, "math_asinh", false)
104
  static math_asinh(
24✔
105
    args: Value[],
106
    source: string,
107
    command: ExprNS.Call,
108
    context: Context,
109
  ): NumberValue {
110
    const x = args[0];
×
111
    if (!isNumeric(x)) {
×
NEW
112
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
113
    }
114

115
    let num: number;
116
    if (x.type === "number") {
×
117
      num = x.value;
×
118
    } else {
119
      num = Number(x.value);
×
120
    }
121

122
    const result = Math.asinh(num);
×
123
    return { type: "number", value: result };
×
124
  }
125

126
  @Validate(1, 1, "math_atan", false)
127
  static math_atan(
24✔
128
    args: Value[],
129
    source: string,
130
    command: ExprNS.Call,
131
    context: Context,
132
  ): NumberValue {
133
    const x = args[0];
×
134
    if (!isNumeric(x)) {
×
NEW
135
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
136
    }
137

138
    let num: number;
139
    if (x.type === "number") {
×
140
      num = x.value;
×
141
    } else {
142
      num = Number(x.value);
×
143
    }
144

145
    const result = Math.atan(num);
×
146
    return { type: "number", value: result };
×
147
  }
148

149
  @Validate(2, 2, "math_atan2", false)
150
  static math_atan2(
24✔
151
    args: Value[],
152
    source: string,
153
    command: ExprNS.Call,
154
    context: Context,
155
  ): NumberValue {
156
    const y = args[0];
×
157
    const x = args[1];
×
158
    if (!isNumeric(x)) {
×
NEW
159
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
UNCOV
160
    } else if (!isNumeric(y)) {
×
NEW
161
      handleRuntimeError(context, new TypeError(source, command, context, y.type));
×
162
    }
163

164
    let yNum: number, xNum: number;
165
    if (y.type === "number") {
×
166
      yNum = y.value;
×
167
    } else {
168
      yNum = Number(y.value);
×
169
    }
170

171
    if (x.type === "number") {
×
172
      xNum = x.value;
×
173
    } else {
174
      xNum = Number(x.value);
×
175
    }
176

177
    const result = Math.atan2(yNum, xNum);
×
178
    return { type: "number", value: result };
×
179
  }
180

181
  @Validate(1, 1, "math_atanh", false)
182
  static math_atanh(
24✔
183
    args: Value[],
184
    source: string,
185
    command: ExprNS.Call,
186
    context: Context,
187
  ): NumberValue {
188
    const x = args[0];
×
189
    if (!isNumeric(x)) {
×
NEW
190
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
191
    }
192

193
    let num: number;
194
    if (x.type === "number") {
×
195
      num = x.value;
×
196
    } else {
197
      num = Number(x.value);
×
198
    }
199

200
    if (num <= -1 || num >= 1) {
×
201
      handleRuntimeError(context, new ValueError(source, command, context, "math_atanh"));
×
202
    }
203

204
    const result = Math.atanh(num);
×
205
    return { type: "number", value: result };
×
206
  }
207

208
  @Validate(1, 1, "math_cos", false)
209
  static math_cos(
24✔
210
    args: Value[],
211
    source: string,
212
    command: ExprNS.Call,
213
    context: Context,
214
  ): NumberValue {
215
    const x = args[0];
7✔
216
    if (!isNumeric(x)) {
7✔
217
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
3✔
218
    }
219

220
    let num: number;
221
    if (x.type === "number") {
4✔
222
      num = x.value;
3✔
223
    } else {
224
      num = Number(x.value);
1✔
225
    }
226

227
    const result = Math.cos(num);
4✔
228
    return { type: "number", value: result };
4✔
229
  }
230

231
  @Validate(1, 1, "math_cosh", false)
232
  static math_cosh(
24✔
233
    args: Value[],
234
    source: string,
235
    command: ExprNS.Call,
236
    context: Context,
237
  ): NumberValue {
238
    const x = args[0];
×
239
    if (!isNumeric(x)) {
×
NEW
240
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
241
    }
242

243
    let num: number;
244
    if (x.type === "number") {
×
245
      num = x.value;
×
246
    } else {
247
      num = Number(x.value);
×
248
    }
249

250
    const result = Math.cosh(num);
×
251
    return { type: "number", value: result };
×
252
  }
253

254
  @Validate(1, 1, "math_degrees", false)
255
  static math_degrees(
24✔
256
    args: Value[],
257
    source: string,
258
    command: ExprNS.Call,
259
    context: Context,
260
  ): NumberValue {
261
    const x = args[0];
×
262
    if (!isNumeric(x)) {
×
NEW
263
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
264
    }
265

266
    let num: number;
267
    if (x.type === "number") {
×
268
      num = x.value;
×
269
    } else {
270
      num = Number(x.value);
×
271
    }
272

273
    const result = (num * 180) / Math.PI;
×
274
    return { type: "number", value: result };
×
275
  }
276

277
  @Validate(1, 1, "math_erf", false)
278
  static math_erf(
24✔
279
    args: Value[],
280
    source: string,
281
    command: ExprNS.Call,
282
    context: Context,
283
  ): NumberValue {
284
    const x = args[0];
×
285
    if (!isNumeric(x)) {
×
NEW
286
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
287
    }
288

289
    let num: number;
290
    if (x.type === "number") {
×
291
      num = x.value;
×
292
    } else {
293
      num = Number(x.value);
×
294
    }
295

296
    const erfnum = erf(num);
×
297

298
    return { type: "number", value: erfnum };
×
299
  }
300

301
  @Validate(1, 1, "math_erfc", false)
302
  static math_erfc(
24✔
303
    args: Value[],
304
    source: string,
305
    command: ExprNS.Call,
306
    context: Context,
307
  ): NumberValue {
308
    const x = args[0];
×
309
    if (!isNumeric(x)) {
×
NEW
310
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
311
    }
312

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

315
    return { type: "number", value: erfc };
×
316
  }
317

318
  @Validate(2, 2, "math_comb", false)
319
  static math_comb(
24✔
320
    args: Value[],
321
    source: string,
322
    command: ExprNS.Call,
323
    context: Context,
324
  ): BigIntValue {
325
    const n = args[0];
×
326
    const k = args[1];
×
327

328
    if (n.type !== "bigint") {
×
NEW
329
      handleRuntimeError(context, new TypeError(source, command, context, n.type));
×
330
    } else if (k.type !== "bigint") {
×
NEW
331
      handleRuntimeError(context, new TypeError(source, command, context, k.type));
×
332
    }
333

334
    const nVal = BigInt(n.value);
×
335
    const kVal = BigInt(k.value);
×
336

337
    if (nVal < 0 || kVal < 0) {
×
338
      handleRuntimeError(context, new ValueError(source, command, context, "math_comb"));
×
339
    }
340

341
    if (kVal > nVal) {
×
342
      return { type: "bigint", value: BigInt(0) };
×
343
    }
344

345
    let result: bigint = BigInt(1);
×
346
    const kk = kVal > nVal - kVal ? nVal - kVal : kVal;
×
347

348
    for (let i: bigint = BigInt(0); i < kk; i++) {
×
349
      result = (result * (nVal - i)) / (i + BigInt(1));
×
350
    }
351

352
    return { type: "bigint", value: result };
×
353
  }
354

355
  @Validate(1, 1, "math_factorial", false)
356
  static math_factorial(
24✔
357
    args: Value[],
358
    source: string,
359
    command: ExprNS.Call,
360
    context: Context,
361
  ): BigIntValue {
362
    const n = args[0];
×
363

364
    if (n.type !== "bigint") {
×
NEW
365
      handleRuntimeError(context, new TypeError(source, command, context, n.type));
×
366
    }
367

368
    const nVal = BigInt(n.value);
×
369

370
    if (nVal < 0) {
×
371
      handleRuntimeError(context, new ValueError(source, command, context, "math_factorial"));
×
372
    }
373

374
    // 0! = 1
375
    if (nVal === BigInt(0)) {
×
376
      return { type: "bigint", value: BigInt(1) };
×
377
    }
378

379
    let result: bigint = BigInt(1);
×
380
    for (let i: bigint = BigInt(1); i <= nVal; i++) {
×
381
      result *= i;
×
382
    }
383

384
    return { type: "bigint", value: result };
×
385
  }
386

387
  static math_gcd(
388
    args: Value[],
389
    source: string,
390
    command: ExprNS.Call,
391
    context: Context,
392
  ): BigIntValue {
393
    if (args.length === 0) {
×
394
      return { type: "bigint", value: BigInt(0) };
×
395
    }
396

397
    const values = args.map(v => {
×
398
      if (v.type !== "bigint") {
×
NEW
399
        handleRuntimeError(context, new TypeError(source, command, context, v.type));
×
400
      }
401
      return BigInt(v.value);
×
402
    });
403

404
    const allZero = values.every(val => val === BigInt(0));
×
405
    if (allZero) {
×
406
      return { type: "bigint", value: BigInt(0) };
×
407
    }
408

409
    let currentGcd: bigint = values[0] < 0 ? -values[0] : values[0];
×
410
    for (let i = 1; i < values.length; i++) {
×
411
      currentGcd = MathBuiltins._gcdOfTwo(currentGcd, values[i] < 0 ? -values[i] : values[i]);
×
412
      if (currentGcd === BigInt(1)) {
×
413
        break;
×
414
      }
415
    }
416

417
    return { type: "bigint", value: currentGcd };
×
418
  }
419

420
  static _gcdOfTwo(a: bigint, b: bigint): bigint {
421
    let x: bigint = a;
×
422
    let y: bigint = b;
×
423
    while (y !== BigInt(0)) {
×
424
      const temp = x % y;
×
425
      x = y;
×
426
      y = temp;
×
427
    }
428
    return x < 0 ? -x : x;
×
429
  }
430

431
  @Validate(1, 1, "math_isqrt", false)
432
  static math_isqrt(
24✔
433
    args: Value[],
434
    source: string,
435
    command: ExprNS.Call,
436
    context: Context,
437
  ): BigIntValue {
438
    const nValObj = args[0];
×
439
    if (nValObj.type !== "bigint") {
×
NEW
440
      handleRuntimeError(context, new TypeError(source, command, context, nValObj.type));
×
441
    }
442

443
    const n: bigint = nValObj.value;
×
444

445
    if (n < 0) {
×
446
      handleRuntimeError(context, new ValueError(source, command, context, "math_isqrt"));
×
447
    }
448

449
    if (n < 2) {
×
450
      return { type: "bigint", value: n };
×
451
    }
452

453
    let low: bigint = BigInt(1);
×
454
    let high: bigint = n;
×
455

456
    while (low < high) {
×
457
      const mid = (low + high + BigInt(1)) >> BigInt(1);
×
458
      const sq = mid * mid;
×
459
      if (sq <= n) {
×
460
        low = mid;
×
461
      } else {
462
        high = mid - BigInt(1);
×
463
      }
464
    }
465

466
    return { type: "bigint", value: low };
×
467
  }
468

469
  static math_lcm(
470
    args: Value[],
471
    source: string,
472
    command: ExprNS.Call,
473
    context: Context,
474
  ): BigIntValue {
475
    if (args.length === 0) {
×
476
      return { type: "bigint", value: BigInt(1) };
×
477
    }
478

479
    const values = args.map(val => {
×
480
      if (val.type !== "bigint") {
×
NEW
481
        handleRuntimeError(context, new TypeError(source, command, context, val.type));
×
482
      }
483
      return BigInt(val.value);
×
484
    });
485

486
    if (values.some(v => v === BigInt(0))) {
×
487
      return { type: "bigint", value: BigInt(0) };
×
488
    }
489

490
    let currentLcm: bigint = MathBuiltins._absBigInt(values[0]);
×
491
    for (let i = 1; i < values.length; i++) {
×
492
      currentLcm = MathBuiltins._lcmOfTwo(currentLcm, MathBuiltins._absBigInt(values[i]));
×
493
      if (currentLcm === BigInt(0)) {
×
494
        break;
×
495
      }
496
    }
497

498
    return { type: "bigint", value: currentLcm };
×
499
  }
500

501
  static _lcmOfTwo(a: bigint, b: bigint): bigint {
502
    const gcdVal: bigint = MathBuiltins._gcdOfTwo(a, b);
×
503
    return BigInt((a / gcdVal) * b);
×
504
  }
505

506
  static _absBigInt(x: bigint): bigint {
507
    return x < 0 ? -x : x;
×
508
  }
509

510
  @Validate(1, 2, "math_perm", true)
511
  static math_perm(
24✔
512
    args: Value[],
513
    source: string,
514
    command: ExprNS.Call,
515
    context: Context,
516
  ): BigIntValue {
517
    const nValObj = args[0];
×
518
    if (nValObj.type !== "bigint") {
×
NEW
519
      handleRuntimeError(context, new TypeError(source, command, context, nValObj.type));
×
520
    }
521
    const n = BigInt(nValObj.value);
×
522

523
    let k = n;
×
524
    if (args.length === 2) {
×
525
      const kValObj = args[1];
×
526
      if (kValObj.type === "none") {
×
527
        k = n;
×
528
      } else if (kValObj.type === "bigint") {
×
529
        k = BigInt(kValObj.value);
×
530
      } else {
NEW
531
        handleRuntimeError(context, new TypeError(source, command, context, kValObj.type));
×
532
      }
533
    }
534

535
    if (n < 0 || k < 0) {
×
536
      handleRuntimeError(context, new ValueError(source, command, context, "math_perm"));
×
537
    }
538

539
    if (k > n) {
×
540
      return { type: "bigint", value: BigInt(0) };
×
541
    }
542

543
    let result: bigint = BigInt(1);
×
544
    for (let i: bigint = BigInt(0); i < k; i++) {
×
545
      result *= n - i;
×
546
    }
547

548
    return { type: "bigint", value: result };
×
549
  }
550

551
  @Validate(1, 1, "math_ceil", false)
552
  static math_ceil(
24✔
553
    args: Value[],
554
    source: string,
555
    command: ExprNS.Call,
556
    context: Context,
557
  ): BigIntValue {
558
    const x = args[0];
×
559

560
    if (x.type === "bigint") {
×
561
      return x;
×
562
    }
563

564
    if (x.type === "number") {
×
565
      const numVal = x.value;
×
566
      const ceiled: bigint = BigInt(Math.ceil(numVal));
×
567
      return { type: "bigint", value: ceiled };
×
568
    }
569

NEW
570
    handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
571
  }
572

573
  @Validate(1, 1, "math_fabs", false)
574
  static math_fabs(
24✔
575
    args: Value[],
576
    source: string,
577
    command: ExprNS.Call,
578
    context: Context,
579
  ): NumberValue {
580
    const x = args[0];
×
581

582
    if (x.type === "bigint") {
×
583
      const bigVal: bigint = BigInt(x.value);
×
584
      const absVal: number = bigVal < 0 ? -Number(bigVal) : Number(bigVal);
×
585
      return { type: "number", value: absVal };
×
586
    }
587

588
    if (x.type === "number") {
×
589
      const numVal: number = x.value;
×
590
      if (typeof numVal !== "number") {
×
NEW
591
        handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
592
      }
593
      const absVal: number = Math.abs(numVal);
×
594
      return { type: "number", value: absVal };
×
595
    }
596

NEW
597
    handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
598
  }
599

600
  @Validate(1, 1, "math_floor", false)
601
  static math_floor(
24✔
602
    args: Value[],
603
    source: string,
604
    command: ExprNS.Call,
605
    context: Context,
606
  ): BigIntValue {
607
    const x = args[0];
×
608

609
    if (x.type === "bigint") {
×
610
      return x;
×
611
    }
612

613
    if (x.type === "number") {
×
614
      const numVal: number = x.value;
×
615
      if (typeof numVal !== "number") {
×
NEW
616
        handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
617
      }
618
      const floored: bigint = BigInt(Math.floor(numVal));
×
619
      return { type: "bigint", value: floored };
×
620
    }
621

NEW
622
    handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
623
  }
624

625
  // Computes the product of a and b along with the rounding error using Dekker's algorithm.
626
  static _twoProd(a: number, b: number): { prod: number; err: number } {
627
    const prod = a * b;
×
628
    const c = 134217729; // 2^27 + 1
×
629
    const a_hi = a * c - (a * c - a);
×
630
    const a_lo = a - a_hi;
×
631
    const b_hi = b * c - (b * c - b);
×
632
    const b_lo = b - b_hi;
×
633
    const err = a_lo * b_lo - (prod - a_hi * b_hi - a_lo * b_hi - a_hi * b_lo);
×
634
    return { prod, err };
×
635
  }
636

637
  // Computes the sum of a and b along with the rounding error using Fast TwoSum.
638
  static _twoSum(a: number, b: number): { sum: number; err: number } {
639
    const sum = a + b;
×
640
    const v = sum - a;
×
641
    const err = a - (sum - v) + (b - v);
×
642
    return { sum, err };
×
643
  }
644

645
  // Performs a fused multiply-add operation: computes (x * y) + z with a single rounding.
646
  static _fusedMultiplyAdd(x: number, y: number, z: number): number {
647
    const { prod, err: prodErr } = MathBuiltins._twoProd(x, y);
×
648
    const { sum, err: sumErr } = MathBuiltins._twoSum(prod, z);
×
649
    const result = sum + (prodErr + sumErr);
×
650
    return result;
×
651
  }
652

653
  static _toNumber(val: Value, source: string, command: ExprNS.Call, context: Context): number {
654
    if (val.type === "bigint") {
×
655
      return Number(val.value);
×
656
    } else if (val.type === "number") {
×
657
      return val.value;
×
658
    } else {
NEW
659
      handleRuntimeError(context, new TypeError(source, command, context, val.type));
×
660
    }
661
  }
662

663
  @Validate(3, 3, "math_fma", false)
664
  static math_fma(
24✔
665
    args: Value[],
666
    source: string,
667
    command: ExprNS.Call,
668
    context: Context,
669
  ): NumberValue {
670
    const xVal = MathBuiltins._toNumber(args[0], source, command, context);
×
671
    const yVal = MathBuiltins._toNumber(args[1], source, command, context);
×
672
    const zVal = MathBuiltins._toNumber(args[2], source, command, context);
×
673

674
    // Special-case handling: According to the IEEE 754 standard, fma(0, inf, nan)
675
    // and fma(inf, 0, nan) should return NaN.
676
    if (isNaN(xVal) || isNaN(yVal) || isNaN(zVal)) {
×
677
      return { type: "number", value: NaN };
×
678
    }
679
    if (xVal === 0 && !isFinite(yVal) && isNaN(zVal)) {
×
680
      return { type: "number", value: NaN };
×
681
    }
682
    if (yVal === 0 && !isFinite(xVal) && isNaN(zVal)) {
×
683
      return { type: "number", value: NaN };
×
684
    }
685

686
    const result = MathBuiltins._fusedMultiplyAdd(xVal, yVal, zVal);
×
687
    return { type: "number", value: result };
×
688
  }
689

690
  @Validate(2, 2, "math_fmod", false)
691
  static math_fmod(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
24✔
692
    // Convert inputs to numbers
693
    const xVal = MathBuiltins._toNumber(args[0], source, command, context);
×
694
    const yVal = MathBuiltins._toNumber(args[1], source, command, context);
×
695

696
    // Divisor cannot be zero
697
    if (yVal === 0) {
×
698
      handleRuntimeError(context, new ValueError(source, command, context, "math_fmod"));
×
699
    }
700

701
    // JavaScript's % operator behaves similarly to C's fmod
702
    // in that the sign of the result is the same as the sign of x.
703
    // For corner cases (NaN, Infinity), JavaScript remainder
704
    // yields results consistent with typical C library fmod behavior.
705
    const remainder = xVal % yVal;
×
706

707
    return { type: "number", value: remainder };
×
708
  }
709

710
  static _roundToEven(num: number): number {
711
    //uses Banker's Rounding as per Python's Round() function
712
    const floorVal = Math.floor(num);
×
713
    const ceilVal = Math.ceil(num);
×
714
    const diffFloor = num - floorVal;
×
715
    const diffCeil = ceilVal - num;
×
716
    if (diffFloor < diffCeil) {
×
717
      return floorVal;
×
718
    } else if (diffCeil < diffFloor) {
×
719
      return ceilVal;
×
720
    } else {
721
      return floorVal % 2 === 0 ? floorVal : ceilVal;
×
722
    }
723
  }
724

725
  @Validate(2, 2, "math_remainder", false)
726
  static math_remainder(
24✔
727
    args: Value[],
728
    source: string,
729
    command: ExprNS.Call,
730
    context: Context,
731
  ): NumberValue {
732
    const x = args[0];
×
733
    const y = args[1];
×
734

735
    let xValue: number;
736
    if (x.type === "bigint") {
×
737
      xValue = Number(x.value);
×
738
    } else if (x.type === "number") {
×
739
      xValue = x.value;
×
740
    } else {
NEW
741
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
742
    }
743

744
    let yValue: number;
745
    if (y.type === "bigint") {
×
746
      yValue = Number(y.value);
×
747
    } else if (y.type === "number") {
×
748
      yValue = y.value;
×
749
    } else {
NEW
750
      handleRuntimeError(context, new TypeError(source, command, context, y.type));
×
751
    }
752

753
    if (yValue === 0) {
×
754
      handleRuntimeError(context, new ValueError(source, command, context, "math_remainder"));
×
755
    }
756

757
    const quotient = xValue / yValue;
×
758
    const n = MathBuiltins._roundToEven(quotient);
×
759
    const remainder = xValue - n * yValue;
×
760

761
    return { type: "number", value: remainder };
×
762
  }
763

764
  @Validate(1, 1, "math_trunc", false)
765
  static math_trunc(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
24✔
766
    const x = args[0];
×
767

768
    if (x.type === "bigint") {
×
769
      return x;
×
770
    }
771

772
    if (x.type === "number") {
×
773
      const numVal: number = x.value;
×
774
      if (typeof numVal !== "number") {
×
NEW
775
        handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
776
      }
777
      let truncated: number;
778
      if (numVal === 0) {
×
779
        truncated = 0;
×
780
      } else if (numVal < 0) {
×
781
        truncated = Math.ceil(numVal);
×
782
      } else {
783
        truncated = Math.floor(numVal);
×
784
      }
785
      return { type: "bigint", value: BigInt(truncated) };
×
786
    }
787

NEW
788
    handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
789
  }
790

791
  @Validate(2, 2, "math_copysign", false)
792
  static math_copysign(
24✔
793
    args: Value[],
794
    source: string,
795
    command: ExprNS.Call,
796
    context: Context,
797
  ): NumberValue {
798
    const [x, y] = args;
×
799

800
    if (!isNumeric(x)) {
×
NEW
801
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
UNCOV
802
    } else if (!isNumeric(y)) {
×
NEW
803
      handleRuntimeError(context, new TypeError(source, command, context, y.type));
×
804
    }
805

806
    const xVal = Number(x.value);
×
807
    const yVal = Number(y.value);
×
808

809
    const absVal = Math.abs(xVal);
×
810
    const isNegative = yVal < 0 || Object.is(yVal, -0);
×
811
    const result = isNegative ? -absVal : absVal;
×
812

813
    return { type: "number", value: Number(result) };
×
814
  }
815

816
  @Validate(1, 1, "math_isfinite", false)
817
  static math_isfinite(
24✔
818
    args: Value[],
819
    source: string,
820
    command: ExprNS.Call,
821
    context: Context,
822
  ): BoolValue {
823
    const xValObj = args[0];
×
824
    if (!isNumeric(xValObj)) {
×
NEW
825
      handleRuntimeError(context, new TypeError(source, command, context, xValObj.type));
×
826
    }
827

828
    const x = Number(xValObj.value);
×
829
    const result: boolean = Number.isFinite(x);
×
830

831
    return { type: "bool", value: result };
×
832
  }
833

834
  @Validate(1, 1, "math_isinf", false)
835
  static math_isinf(
24✔
836
    args: Value[],
837
    source: string,
838
    command: ExprNS.Call,
839
    context: Context,
840
  ): BoolValue {
841
    const xValObj = args[0];
×
842
    if (!isNumeric(xValObj)) {
×
NEW
843
      handleRuntimeError(context, new TypeError(source, command, context, xValObj.type));
×
844
    }
845

846
    const x = Number(xValObj.value);
×
847
    const result: boolean = x === Infinity || x === -Infinity;
×
848

849
    return { type: "bool", value: result };
×
850
  }
851

852
  @Validate(1, 1, "math_isnan", false)
853
  static math_isnan(
24✔
854
    args: Value[],
855
    source: string,
856
    command: ExprNS.Call,
857
    context: Context,
858
  ): BoolValue {
859
    const xValObj = args[0];
×
860
    if (!isNumeric(xValObj)) {
×
NEW
861
      handleRuntimeError(context, new TypeError(source, command, context, xValObj.type));
×
862
    }
863

864
    const x = Number(xValObj.value);
×
865
    const result: boolean = Number.isNaN(x);
×
866

867
    return { type: "bool", value: result };
×
868
  }
869

870
  @Validate(2, 2, "math_ldexp", false)
871
  static math_ldexp(
24✔
872
    args: Value[],
873
    source: string,
874
    command: ExprNS.Call,
875
    context: Context,
876
  ): NumberValue {
877
    const xVal = MathBuiltins._toNumber(args[0], source, command, context);
×
878

879
    if (args[1].type !== "bigint") {
×
NEW
880
      handleRuntimeError(context, new TypeError(source, command, context, args[1].type));
×
881
    }
882
    const expVal = args[1].value;
×
883

884
    // Perform x * 2^expVal
885
    // In JavaScript, 2**expVal may overflow or underflow, yielding Infinity or 0 respectively.
886
    // That behavior parallels typical C library rules for ldexp.
887
    const result = xVal * Math.pow(2, Number(expVal));
×
888

889
    return { type: "number", value: result };
×
890
  }
891

892
  @Validate(2, 2, "math_nextafter", false)
893
  static math_nextafter(
24✔
894
    _args: Value[],
895
    _source: string,
896
    _command: ExprNS.Call,
897
    _context: Context,
898
  ): Value {
899
    // TODO: Implement math_nextafter using proper bit-level manipulation and handling special cases (NaN, Infinity, steps, etc.)
900
    throw new Error("math_nextafter not implemented");
×
901
  }
902

903
  @Validate(1, 1, "math_ulp", false)
904
  static math_ulp(
24✔
905
    _args: Value[],
906
    _source: string,
907
    _command: ExprNS.Call,
908
    _context: Context,
909
  ): Value {
910
    // TODO: Implement math_ulp to return the unit in the last place (ULP) of the given floating-point number.
911
    throw new Error("math_ulp not implemented");
×
912
  }
913

914
  @Validate(1, 1, "math_cbrt", false)
915
  static math_cbrt(
24✔
916
    args: Value[],
917
    source: string,
918
    command: ExprNS.Call,
919
    context: Context,
920
  ): NumberValue {
921
    const xVal = args[0];
×
922
    let x: number;
923

924
    if (xVal.type !== "number") {
×
925
      if (xVal.type === "bigint") {
×
926
        x = Number(xVal.value);
×
927
      } else {
NEW
928
        handleRuntimeError(context, new TypeError(source, command, context, xVal.type));
×
929
      }
930
    } else {
931
      x = xVal.value;
×
932
    }
933

934
    const result = Math.cbrt(x);
×
935

936
    return { type: "number", value: result };
×
937
  }
938

939
  @Validate(1, 1, "math_exp", false)
940
  static math_exp(
24✔
941
    args: Value[],
942
    source: string,
943
    command: ExprNS.Call,
944
    context: Context,
945
  ): NumberValue {
946
    const xVal = args[0];
×
947
    let x: number;
948

949
    if (xVal.type !== "number") {
×
950
      if (xVal.type === "bigint") {
×
951
        x = Number(xVal.value);
×
952
      } else {
NEW
953
        handleRuntimeError(context, new TypeError(source, command, context, xVal.type));
×
954
      }
955
    } else {
956
      x = xVal.value;
×
957
    }
958

959
    const result = Math.exp(x);
×
960
    return { type: "number", value: result };
×
961
  }
962

963
  @Validate(1, 1, "math_exp2", false)
964
  static math_exp2(
24✔
965
    args: Value[],
966
    source: string,
967
    command: ExprNS.Call,
968
    context: Context,
969
  ): NumberValue {
970
    const xVal = args[0];
×
971
    let x: number;
972

973
    if (xVal.type !== "number") {
×
974
      if (xVal.type === "bigint") {
×
975
        x = Number(xVal.value);
×
976
      } else {
NEW
977
        handleRuntimeError(context, new TypeError(source, command, context, xVal.type));
×
978
      }
979
    } else {
980
      x = xVal.value;
×
981
    }
982

983
    const result = Math.pow(2, x);
×
984
    return { type: "number", value: result };
×
985
  }
986

987
  @Validate(1, 1, "math_expm1", false)
988
  static math_expm1(
24✔
989
    args: Value[],
990
    source: string,
991
    command: ExprNS.Call,
992
    context: Context,
993
  ): NumberValue {
994
    const x = args[0];
×
995
    if (!isNumeric(x)) {
×
NEW
996
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
997
    }
998

999
    let num: number;
1000
    if (x.type === "number") {
×
1001
      num = x.value;
×
1002
    } else {
1003
      num = Number(x.value);
×
1004
    }
1005

1006
    const result = Math.expm1(num);
×
1007
    return { type: "number", value: result };
×
1008
  }
1009

1010
  @Validate(1, 1, "math_gamma", false)
1011
  static math_gamma(
24✔
1012
    args: Value[],
1013
    source: string,
1014
    command: ExprNS.Call,
1015
    context: Context,
1016
  ): NumberValue {
1017
    const x = args[0];
×
1018
    if (!isNumeric(x)) {
×
NEW
1019
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
1020
    }
1021

1022
    const z = MathBuiltins._toNumber(x, source, command, context);
×
1023
    const result = gamma(z);
×
1024

1025
    return { type: "number", value: result };
×
1026
  }
1027

1028
  @Validate(1, 1, "math_lgamma", false)
1029
  static math_lgamma(
24✔
1030
    args: Value[],
1031
    source: string,
1032
    command: ExprNS.Call,
1033
    context: Context,
1034
  ): NumberValue {
1035
    const x = args[0];
×
1036
    if (!isNumeric(x)) {
×
NEW
1037
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
1038
    }
1039

1040
    const z = MathBuiltins._toNumber(x, source, command, context);
×
1041
    const result = lgamma(z);
×
1042

1043
    return { type: "number", value: result };
×
1044
  }
1045

1046
  @Validate(1, 2, "math_log", true)
1047
  static math_log(
24✔
1048
    args: Value[],
1049
    source: string,
1050
    command: ExprNS.Call,
1051
    context: Context,
1052
  ): NumberValue {
1053
    const x = args[0];
×
1054
    if (!isNumeric(x)) {
×
NEW
1055
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
1056
    }
1057
    let num: number;
1058
    if (x.type === "number") {
×
1059
      num = x.value;
×
1060
    } else {
1061
      num = Number(x.value);
×
1062
    }
1063

1064
    if (num <= 0) {
×
1065
      handleRuntimeError(context, new ValueError(source, command, context, "math_log"));
×
1066
    }
1067

1068
    if (args.length === 1) {
×
1069
      return { type: "number", value: Math.log(num) };
×
1070
    }
1071

1072
    const baseArg = args[1];
×
1073
    if (!isNumeric(baseArg)) {
×
NEW
1074
      handleRuntimeError(context, new TypeError(source, command, context, baseArg.type));
×
1075
    }
1076
    let baseNum: number;
1077
    if (baseArg.type === "number") {
×
1078
      baseNum = baseArg.value;
×
1079
    } else {
1080
      baseNum = Number(baseArg.value);
×
1081
    }
1082
    if (baseNum <= 0) {
×
1083
      handleRuntimeError(context, new ValueError(source, command, context, "math_log"));
×
1084
    }
1085

1086
    const result = Math.log(num) / Math.log(baseNum);
×
1087
    return { type: "number", value: result };
×
1088
  }
1089

1090
  @Validate(1, 1, "math_log10", false)
1091
  static math_log10(
24✔
1092
    args: Value[],
1093
    source: string,
1094
    command: ExprNS.Call,
1095
    context: Context,
1096
  ): NumberValue {
1097
    const x = args[0];
×
1098
    if (!isNumeric(x)) {
×
NEW
1099
      handleRuntimeError(context, new TypeError(source, command, context, args[0].type));
×
1100
    }
1101
    let num: number;
1102
    if (x.type === "number") {
×
1103
      num = x.value;
×
1104
    } else {
1105
      num = Number(x.value);
×
1106
    }
1107
    if (num <= 0) {
×
1108
      handleRuntimeError(context, new ValueError(source, command, context, "math_log10"));
×
1109
    }
1110

1111
    const result = Math.log10(num);
×
1112
    return { type: "number", value: result };
×
1113
  }
1114

1115
  @Validate(1, 1, "math_log1p", false)
1116
  static math_log1p(
24✔
1117
    args: Value[],
1118
    source: string,
1119
    command: ExprNS.Call,
1120
    context: Context,
1121
  ): NumberValue {
1122
    const x = args[0];
×
1123
    if (!isNumeric(x)) {
×
NEW
1124
      handleRuntimeError(context, new TypeError(source, command, context, args[0].type));
×
1125
    }
1126
    let num: number;
1127
    if (x.type === "number") {
×
1128
      num = x.value;
×
1129
    } else {
1130
      num = Number(x.value);
×
1131
    }
1132
    if (1 + num <= 0) {
×
1133
      handleRuntimeError(context, new ValueError(source, command, context, "math_log1p"));
×
1134
    }
1135

1136
    const result = Math.log1p(num);
×
1137
    return { type: "number", value: result };
×
1138
  }
1139

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

1161
    const result = Math.log2(num);
×
1162
    return { type: "number", value: result };
×
1163
  }
1164

1165
  @Validate(2, 2, "math_pow", false)
1166
  static math_pow(
24✔
1167
    args: Value[],
1168
    source: string,
1169
    command: ExprNS.Call,
1170
    context: Context,
1171
  ): NumberValue {
1172
    const base = args[0];
×
1173
    const exp = args[1];
×
1174

1175
    if (!isNumeric(base)) {
×
NEW
1176
      handleRuntimeError(context, new TypeError(source, command, context, base.type));
×
UNCOV
1177
    } else if (!isNumeric(exp)) {
×
NEW
1178
      handleRuntimeError(context, new TypeError(source, command, context, exp.type));
×
1179
    }
1180

1181
    let baseNum: number;
1182
    if (base.type === "number") {
×
1183
      baseNum = base.value;
×
1184
    } else {
1185
      baseNum = Number(base.value);
×
1186
    }
1187

1188
    let expNum: number;
1189
    if (exp.type === "number") {
×
1190
      expNum = exp.value;
×
1191
    } else {
1192
      expNum = Number(exp.value);
×
1193
    }
1194

1195
    const result = Math.pow(baseNum, expNum);
×
1196
    return { type: "number", value: result };
×
1197
  }
1198

1199
  @Validate(1, 1, "math_radians", false)
1200
  static math_radians(
24✔
1201
    args: Value[],
1202
    source: string,
1203
    command: ExprNS.Call,
1204
    context: Context,
1205
  ): NumberValue {
1206
    const x = args[0];
×
1207
    if (!isNumeric(x)) {
×
NEW
1208
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
1209
    }
1210

1211
    let deg: number;
1212
    if (x.type === "number") {
×
1213
      deg = x.value;
×
1214
    } else {
1215
      deg = Number(x.value);
×
1216
    }
1217

1218
    const radians = (deg * Math.PI) / 180;
×
1219
    return { type: "number", value: radians };
×
1220
  }
1221

1222
  @Validate(1, 1, "math_sin", false)
1223
  static math_sin(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
24✔
1224
    const x = args[0];
7✔
1225
    if (!isNumeric(x)) {
7✔
1226
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
3✔
1227
    }
1228

1229
    let num: number;
1230
    if (x.type === "number") {
4✔
1231
      num = x.value;
3✔
1232
    } else {
1233
      num = Number(x.value);
1✔
1234
    }
1235

1236
    const result = Math.sin(num);
4✔
1237
    return { type: "number", value: result };
4✔
1238
  }
1239

1240
  @Validate(1, 1, "math_sinh", false)
1241
  static math_sinh(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
24✔
1242
    const x = args[0];
×
1243
    if (!isNumeric(x)) {
×
NEW
1244
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
1245
    }
1246

1247
    let num: number;
1248
    if (x.type === "number") {
×
1249
      num = x.value;
×
1250
    } else {
1251
      num = Number(x.value);
×
1252
    }
1253

1254
    const result = Math.sinh(num);
×
1255
    return { type: "number", value: result };
×
1256
  }
1257

1258
  @Validate(1, 1, "math_tan", false)
1259
  static math_tan(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
24✔
1260
    const x = args[0];
×
1261
    if (!isNumeric(x)) {
×
NEW
1262
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
1263
    }
1264

1265
    let num: number;
1266
    if (x.type === "number") {
×
1267
      num = x.value;
×
1268
    } else {
1269
      num = Number(x.value);
×
1270
    }
1271

1272
    const result = Math.tan(num);
×
1273
    return { type: "number", value: result };
×
1274
  }
1275

1276
  @Validate(1, 1, "math_tanh", false)
1277
  static math_tanh(
24✔
1278
    args: Value[],
1279
    source: string,
1280
    command: ExprNS.Call,
1281
    context: Context,
1282
  ): NumberValue {
1283
    const x = args[0];
×
1284
    if (!isNumeric(x)) {
×
NEW
1285
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
1286
    }
1287

1288
    let num: number;
1289
    if (x.type === "number") {
×
1290
      num = x.value;
×
1291
    } else {
1292
      num = Number(x.value);
×
1293
    }
1294

1295
    const result = Math.tanh(num);
×
1296
    return { type: "number", value: result };
×
1297
  }
1298

1299
  @Validate(1, 1, "math_sqrt", false)
1300
  static math_sqrt(
24✔
1301
    args: Value[],
1302
    source: string,
1303
    command: ExprNS.Call,
1304
    context: Context,
1305
  ): NumberValue {
1306
    const x = args[0];
×
1307
    if (!isNumeric(x)) {
×
NEW
1308
      handleRuntimeError(context, new TypeError(source, command, context, x.type));
×
1309
    }
1310

1311
    let num: number;
1312
    if (x.type === "number") {
×
1313
      num = x.value;
×
1314
    } else {
1315
      num = Number(x.value);
×
1316
    }
1317

1318
    if (num < 0) {
×
1319
      handleRuntimeError(context, new ValueError(source, command, context, "math_sqrt"));
×
1320
    }
1321

1322
    const result = Math.sqrt(num);
×
1323
    return { type: "number", value: result };
×
1324
  }
1325
}
1326

1327
for (const builtin of Object.getOwnPropertyNames(MathBuiltins)) {
24✔
1328
  if (
1,440✔
1329
    typeof MathBuiltins[builtin as keyof typeof MathBuiltins] === "function" &&
2,808✔
1330
    !builtin.startsWith("_")
1331
  ) {
1332
    mathBuiltins.set(builtin, {
1,176✔
1333
      type: "builtin",
1334
      func: MathBuiltins[builtin as keyof typeof MathBuiltins] as BuiltinValue["func"],
1335
      name: builtin,
1336
      minArgs: minArgMap.get(builtin) || 0,
1,224✔
1337
    });
1338
  }
1339
}
1340
for (const [name, info] of Object.entries(constantMap)) {
24✔
1341
  mathBuiltins.set(name, info);
120✔
1342
}
1343

1344
export default {
24✔
1345
  name: GroupName.MATH,
1346
  prelude: "",
1347
  builtins: mathBuiltins,
1348
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc