← board

math: the remaining missing names, and degrees() associates the wrong way

1. degrees() — a real value divergence, one line to fix

math.degrees(3.14)   pxx: 179.90874767107852   CPython: 179.9087476710785

Measured directly: pxx's answer is exactly 3.14 * 180 / math.pi and CPython's is exactly 3.14 * (180 / math.pi). The conversion factor must be formed FIRST and then multiplied — the same constant CPython uses — rather than multiplying by 180 and dividing by pi. radians() agrees already, so only degrees is wrong.

2. Names that do not resolve at all

Each is error: undefined variable:

name CPython
math.asin(0) 0.0
math.acos(1) 0.0
math.atan(1) 0.7853981633974483
math.fsum([...]) exact compensated sum
math.modf(2.5) (0.5, 2.0)
math.perm(5, 2) 20
math.dist([0,0], [3,4]) 5.0
math.prod([1,2,3]) 6

sin/cos/tan/hypot/comb/gcd/factorial/isclose/fmod/copysign /log2/log10/exp/sqrt/floor/ceil/inf/nan/pi/e are all present and agree, so this is a short tail rather than a hole.

fsum is worth doing together with [[bug-nilpy-sum-of-floats-has-no-compensated-summation]] — same algorithm, two callers.

3. import random does not exist

random.random() / random.randint() / random.seed() are all "undefined variable". Not a divergence so much as an absent module, but it is one of the first imports a script reaches for, and seeding makes it testable against CPython only for the shape (a float in [0,1), an int in range) — the sequence itself is deliberately implementation-defined, so the gate must assert the contract, not the values.

Gate

A .npy diffed against CPython: degrees over several angles (including the row above, which is what shows the association), every name in the table with a value CPython pins exactly, and — if random lands here rather than in its own ticket — range/type assertions plus seed() making two runs of the same program agree with each other.

Resolution (2026-08-15)

All three sections done, with one deliberate exclusion.

1. degrees()lib/rtl/math.pas now forms the conversion FACTOR first (r * (180/pi), and d * (pi/180) for the sibling), which is how both CPython and FPC associate it. math.degrees(3.14) is 179.9087476710785. The fix lands in a Track B file because that is where RadToDeg lives; it is two lines, additive to nothing, and it corrects the PASCAL frontend's FPC parity by the same edit.

2. The eight missing names — seven landed, one refused for a stated reason.

Two things the CPython diff caught that reasoning would not have:

3. random exists nowseed, random, randint, randrange, uniform, choice, shuffle, on pylib's own SplitMix64. The sequence is deliberately not CPython's (its Mersenne stream is an implementation detail there too), so the test asserts the CONTRACT: random() in [0, 1), randint(a, b) inclusive at both ends, a repeated seed repeating, choice() an element of the sequence, shuffle() a permutation that mutates in place and returns None. The state starts at a FIXED value rather than a clock reading, so a program that never seeds still reproduces — which is what makes a failure reportable.

Gate: test/test_nilpy_math_surface_and_random.npy (+.expected, wired into the Makefile). The math half is byte-identical to CPython; the random half asserts the contract. tools/gate.sh quick GREEN, self-host byte-identical.

Log