1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
The width of a number is part of its meaning. The failure behind this chapter: a Modula-2 compiler mapped LONGREAL to the x87 80-bit format, which occupies sixteen bytes in memory — so a loop reading 8-byte doubles off the wire strode through them at the wrong width and read garbage from the second element on. Nothing warned, because the language's own type said "a long real", and what that meant depended on the compiler.
In M9 every numeric type names its width — I8..I64, U8..U64, F32, F64, BYTE — and nothing converts implicitly, not even the "safe" direction:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
the compiler refuses:
19:5 X2Assign body: cannot assign F64 to I64 (no implicit conversions, par 2.1)
That diagnostic is real — the gate compiles this file on every run and requires exactly that refusal. The assignment must be written as what it is: a conversion, i := I64 (x) — which brings us to the second rule.
I64 (x) is an ordinary-looking call with a checked meaning: if the value does not fit the target, it raises ValueRange. The museum piece behind it: Trunc(NaN) crashed one runtime and, in a well-known array library, silently answers the most negative integer — a number that then flows onward into someone's mean.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
expected output:
I32 fits: 2000000000 I32 (3000000000) raised ValueRange I64 (NaN) raised ValueRange
Note the shape: each probe procedure carries its own EXCEPT block, because a handler belongs to the frame that knows what the failure means there. NaN travels freely between floats — that is IEEE arithmetic and it is right — and is stopped at the integer boundary, where "not a number" has no honest representation.
A procedure that performs a checked conversion has two options: handle the failure, or declare it. There is no third option in which the signature stays quiet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
the compiler refuses:
12:1 X2Raises.Narrow: unhandled RAISES ValueRange from I32 conversion
This is what makes a RAISES list worth reading: it is not documentation that can rot, it is checked arithmetic over the call graph. When chapter 3 shows a definition promising RAISES TooCold, that promise is proven, not asserted.
Checked + raises Overflow — always, in every build, for the reason chapter 1 gave. Sometimes modular arithmetic is the point (hashing, checksums, random-number generators). M9 spells that as different operators — +%, -%, *% — defined modulo 2^64, so the intent is visible in the source and greppable forever:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
expected output:
a + b = 9223372036854775807 a + b raised Overflow top +% 1 = -9223372036854775808
MAX (I64) +% 1 lands exactly on the most negative I64, printed with all its digits.
← Previous: hello, and why M9 exists · Next: definition and implementation →