Numbers
Integers and floats, size as a suffix, sign as a prefix — and division, which does not do what you expect.
Archon has two families of numbers, and the border between them is visible to the naked eye: a literal with a point is a float, without one it is an integer. That difference changes the result of a computation, and it is the subject of this sub-section.
@entry(start)
object MyProgram {
start() {
var total := 7;
var parts := 3;
Console.print(text: $"{total} / {parts} = {total / parts}, remainder {total % parts}");
Console.print(text: $"in floats: {7.0 / 3.0}");
}
}
Read the first line of output before running it, then run it. 7 / 3 = 2.
Integer division truncates
This is not rounding, it is truncation: 7 / 3 is 2, not 2.33 and not 3. Dividing two
integers yields an integer, because the result of an operation between two integers is an
integer — the rule has no exception, and the language would rather hold it than change type
behind your back.
What was thrown away is not lost for all that: % gives it back. 7 % 3 is 1, the
remainder. Together the two tell the whole of the division: 7 is three times 2, and 1 is left.
To get 2.33, you have to ask for a division of floats — hence the program’s second line,
7.0 / 3.0. The point changes everything: two floats divided yield a float.
int, float, and size as a suffix
An integer is an int, a float a float. The size in bits is an optional suffix, stuck to
the name:
var a: int := 5; // the machine word size
var b: int32 := 5; // 32 bits, guaranteed, whatever the machine
var c: int64 := 5;
var x: float := 3.14; // same logic
var y: float64 := 3.14;
The names are written out in full, never abbreviated — int32, never i32. And the suffix is
written only when the size matters: a binary structure, a file format, a call into a library
written elsewhere. When you simply want “an integer”, int is enough, and the compiler takes the
machine word size.
Unlike C’s int, that choice is settled once and for all: it will not change from one
platform to another under your feet.
unsigned — an integer that never goes below zero
An integer is signed by default: it can be negative. When a value never will be — a count,
an index, a byte — the word unsigned says so, as a prefix, written out in full as well:
var counter: unsigned int := 5; // never negative, the machine word size
var octet: unsigned int8 := 200; // from 0 to 255
This is not an int with a higher ceiling, it is another type, whose values start at zero
— and the compiler holds it to that. A literal that does not fit is refused before anything
runs; var counter: unsigned int := -1; does not pass:
error[T033]: the literal '-1' does not fit in 'unsigned int64', whose values run from 0 to 18446744073709551615
And a computation that would leave the range stops: 0 - 1 on an unsigned has nowhere to go,
the program raises an OverflowError — an error of the same kind as integer division by zero,
just below. Another rule to keep in mind: signed and unsigned do not mix. Adding an int
and an unsigned int is refused, and it is up to you to say which of the two you meant.
Finally,
unsigned applies to integers only — an unsigned float makes no sense, and the compiler says
so.
Dividing by zero: an error among integers, a value among floats
var zero := 0;
Console.print(text: $"{7 / zero}");
The program stops, and says so:
uncaught error — DivideByZeroError : division by zero
This is a choice, and it is consistent with the rest of the language: among integers, a division by zero has no result — no integer could carry it — so the program does not invent one. It returns neither zero nor some arbitrary number: it raises an error, which a program can catch if it expects it. How to catch one is the subject of section 8.
Among floats the answer is the opposite, and the very same reason gives it: they do have something to carry the result in.
7.0 / 0.0 // inf
-7.0 / 0.0 // -inf
0.0 / 0.0 // nan
inf is infinity, nan is “not a number” — two values the float standard, IEEE 754, defines,
and that a float holds the way it holds 3.14. The language spells them itself, always the
same way, rather than letting the machine choose its own spelling.
So keep the rule the right way round: it is not zero that raises, it is the integer zero.
Your turn
- Change
7to9and read again:9 / 3 = 3, remainder 0. A remainder of zero is a division that comes out even. - Put a point on only one of the two numbers —
7.0 / 3— and read what the compiler answers. Mixing an integer and a float does not pass in silence: it asks you to say it was intended. - Replace
partswith0and run. The error names what happened, and the program stops there rather than carrying on with an invented result. - Then put a point on both numbers —
7.0 / 0.0— and run: this time nothing stops, andinfis printed. - Declare
var counter: unsigned int := 0;then, on the next line,counter := counter - 1;. Run it: there is no −1 among the unsigned, and the program stops and says so. Then put-1directly in the declaration: this time the refusal comes before anything runs, and it names the permitted range.