prelude.Int

Part of prelude.

let Int.abs (a: Int): Int

The absolute value.

Int.abs of the most negative Int cannot be represented and overflows, which stops the program rather than returning a negative "absolute" value.

let Int.min (a: Int) (b: Int): Int

The smaller of two.

let Int.max (a: Int) (b: Int): Int

The larger of two.

let Int.clamp (x: Int) (lo: Int) (hi: Int): Int

x held between lo and hi inclusive. A lo above hi gives hi, since the upper bound is applied last.

let Int.shr_logical (x: Int) (n: Int): Int

An UNSIGNED right shift: zeros are shifted in, never the sign bit. >> is arithmetic, because Int is signed and -8 >> 1 should be -4; hashing wants the other one, and writing it by hand is exactly the sort of thing that is wrong at n = 0 and n = 64 in every codebase that tries.

The mask is ~(-1 << (64 - n)) and NOT (1 << (64 - n)) - 1, which is the spelling everyone reaches for first and which traps here: at n = 1 that is (1 << 63) - 1, and 1 << 63 is Int's most negative value, so subtracting one from it is an integer overflow. The complement builds the same mask out of a shift and a NOT, neither of which can overflow.

The two ends are handled before the mask because the mask cannot express them: at n = 0 it would need all 64 bits, and 64 - 0 is a shift of 64, which this language defines as zero.

let Int.count_ones (x: Int): Int

The number of one bits. Counted a nibble at a time through a lookup rather than bit by bit, so it is 16 steps and not 64.

let Int.to_radix (n: Int) (radix: Int): String

--- Radix ---

SIGNED, and reversible: Int.to_radix(-255, 16) is "-ff", which String.parse_int would read back. That is the opposite of what C and Rust print for %x on a negative, and it is the choice that keeps to_radix a number conversion rather than a bit dump. For the bit pattern, Int.to_bits says so in its name.

Radix is clamped to 2..36: one digit per character, 0-9a-z.

let Int.to_hex (n: Int): String

Lowercase hex, SIGNED: -255 is -ff, not a two's-complement bit pattern. Int.to_hex_bits is the one that shows the bits.

let Int.to_binary (n: Int): String

Binary, SIGNED: -1 is -1. Int.to_bits shows all 64 bits.

let Int.to_octal (n: Int): String

Octal, signed. See Int.to_radix for other bases.

let Int.to_bits (n: Int): String

The BIT PATTERN: all 64 bits, two's complement, most significant first. This is the one to reach for with a bit field, where a signed reading is not what is wanted. Int.to_bits(-1) is sixty-four ones, while Int.to_binary(-1) is "-1". Both are right about different questions.

let Int.to_hex_bits (n: Int): String

The same, in hex: sixteen digits, two's complement, zero padded.

let Int.wrapping_add (a: Int) (b: Int): Int

Addition that WRAPS on overflow, two's complement, instead of stopping the program.

+ is the one to reach for. It aborts on overflow, deliberately, because an integer that silently became negative is a wrong answer that keeps running. This is for the cases where wrapping is the SPECIFICATION rather than an accident: hashes, checksums, fixed-width binary protocols, and generators like PCG or splitmix64 that are defined mod 2^64.

It is a named function and not an operator so that it is visible at the call site. Wrapping should be asked for, not defaulted to.

let Int.wrapping_sub (a: Int) (b: Int): Int

Subtraction that WRAPS on overflow. See Int.wrapping_add.

let Int.wrapping_mul (a: Int) (b: Int): Int

Multiplication that WRAPS on overflow. See Int.wrapping_add.

This is the one most algorithms actually need: FNV-1a, xxHash and splitmix64 are all a wrapping multiply by a large constant followed by a mix.

let Int.to_float (self: Int): Float

The integer as a Float. Plum never converts between numeric types on its own, so this is how it is said.

Implemented by the compiler itself, so it has no Plum source to read.