LEVIATHAN v962456e · 962456eee1

Types

Sized integers: byte, int8, int16, int32, uint

The fixed-width integer types, how arithmetic wraps, how mixed widths combine, and when conversions throw.

since 0.1.0-alpha.1linuxwindowswasm

Description

Besides the 64-bit int, the language has five fixed-width integer types:

type width range
byte 8 bits, unsigned 0 to 255
int8 8 bits, signed -128 to 127
int16 16 bits, signed -32768 to 32767
int32 32 bits, signed -2147483648 to 2147483647
uint 32 bits, unsigned 0 to 4294967295

They default to 0 and support the full integer operator set: + - * / %, & | ^ << >>, unary - and ~, the compound assignments and the comparisons. There is no uint8, uint16 or uint64; byte is the unsigned 8-bit type.

Arithmetic wraps silently at the type's own width. A conversion that cannot represent its value throws instead.

Wrapping arithmetic

byte b = 255;
b = b + 1;
console.writeln(b);
byte z = 0;
z = z - 1;
console.writeln(z);
int8 s = 127;
s = s + 1;
console.writeln(s);
int8 neg = -8;
console.writeln(neg >> 1);
uint u = 0;
u = u - 1;
console.writeln(u);
uint big = 3000000000;
console.writeln(big / 2);
console.writeln(big > 5);
0
255
-128
-4
4294967295
1500000000
true

Rules

Wrapping.

  • Arithmetic wraps at the working width. For a signed type the result is two's complement: 127 + 1 as int8 is -128. uint arithmetic wraps modulo 2 to the 32nd power: 0 - 1 is 4294967295.
  • ~ reduces to the type's width: for a byte zero it gives 255, for a uint zero it gives 4294967295.
  • >> on a signed type is an arithmetic shift, so int8 -8 >> 1 is -4. On byte and uint it is a logical shift. A uint above 2 to the 31st power keeps its true value: 3000000000 / 2 is 1500000000.
  • Division or remainder by zero throws a catchable RuntimeException.
  • A shift count must be inside the working width: 0..7 for an 8-bit result, 0..15, 0..31 and 0..63 for 16, 32 and 64 bits. Outside that range the shift throws. A shift by an int variable computes in int, so its bound is 0..63.
  • A minimum value divided by -1 wraps at every sized width.

Mixed widths. Two operands of different types are combined at a common working type:

  • Two signed integers, or two unsigned ones, give the wider of the two: byte + uint is uint, int8 + int16 is int16, byte + int is int.
  • A signed and an unsigned integer give the smallest signed type whose range covers both: int8 + byte is int16, int32 + byte is int32. uint with any signed type, including int32, gives int, because covering uint needs 33 signed bits.
  • A bare literal beside a typed operand takes that operand's type, so b * 2 with a byte b computes at 8 bits. A variable of type int does not: b << count with int count computes in int.
  • The result of every operator is reduced to the working type, so wrapping happens at the joined width, not at the width of the left operand.

Literals and conversions.

  • A literal stored in a sized type is range-checked at compile time. byte b = 200; is fine; byte b = 256; and byte b = -1; are compile errors that name the range.
  • Storing an integer value into a narrower type is a conversion and throws when the value does not fit. int8 c = b; throws for a byte b of 200.
  • int.toByte(), toInt8(), toInt16(), toInt32() and toUint() convert from int and throw when the value is out of range. toInt() on a sized integer always succeeds. To narrow without throwing, mask first: (n & 255).toByte().
  • byte and uint have no abs() or sign(). Calling a method that does not exist on a primitive is not a compile error; it throws a RuntimeException reading "cannot resolve call target" when the program runs. int8, int16 and int32 have abs() and sign(); abs() of the minimum value wraps to itself.

Distinct types. Each width is its own type at run time. is and match tell a byte from an int holding the same number, and values of different widths are different keys in a Map.

Examples

Joined widths and shift limits:

Mixed widths and shifts

byte b = 200;
int8 i8 = -5;
console.writeln(b + 100);
console.writeln(i8 + b);
console.writeln((i8 + b) is int16);
console.writeln((b + 1) is byte);
int count = 8;
console.writeln(b << count);
try {
    console.writeln(b << 8);
} catch (RuntimeException e) {
    console.writeln("caught: ${e.message}");
}
44
195
true
true
51200
caught: shift count out of range

Conversions throw; masking does not:

Conversions that throw

int n = 300;
byte b = 200;
try {
    console.writeln(n.toByte());
} catch (RuntimeException e) {
    console.writeln("caught: ${e.message}");
}
try {
    int8 c = b;
    console.writeln(c);
} catch (RuntimeException e) {
    console.writeln("caught: ${e.message}");
}
console.writeln((n & 255).toByte());
console.writeln(b.toInt() + 100);
caught: toByte: value out of range (0..255)
caught: value 200 out of range for 'int8' (-128..127)
44
300

A byte and an int with the same value are distinguishable in a union:

Sized integers in a union

byte b = 7;
int n = 7;
int | byte either = b;
match (either) {
    byte => console.writeln("a byte");
    int => console.writeln("an int");
}
either = n;
match (either) {
    byte => console.writeln("a byte");
    int => console.writeln("an int");
}
a byte
an int

Notes

The methods of the sized integers (toString, toHex, min, max, clamp, pow, toInt, toFloat and the others) are listed in the library reference. See lang.numeric-conversions for how a value is converted when it is stored into a typed slot.

See also

  • Numeric conversions and working types — How operands of different numeric types combine, and when a value is converted on its way into a typed variable, field, parameter or return.
  • Sized floats: float8, float16, float32 — The narrow floating-point formats, how every operation re-rounds, and where they overflow or throw.
  • int — The signed 64-bit integer type, the default type of whole numbers.