Skip to content
GitHub
ReferenceIntegers

Integers

Integer arithmetic in Calx matches Java's bit for bit - overflow, division, shifts and literals.

All 6 examples tested by CIEdit this pageReport a problem

Calx integers behave exactly as Java’s, so code ported from Java computes what it computed there. Each rule here says what Calx does and how the emitted C does it, since every one of them is undefined or implementation-defined in C.

Rule Calx Emitted C
Overflow +, -, * and unary - wrap (two’s complement) for every integer type, as in Java Arithmetic on the unsigned type of the same width, then conversion back. Also built with -fwrapv
Shifts <<, >> and >>> work on i32, i64, u32 and u64. A variable count is masked to 31 or 63, as in Java. >> is arithmetic on signed types, and >>> is logical x << (n & 31)
MIN / -1 Gives MIN. MIN % -1 gives 0 A branch on -1 before dividing, which traps on x86 otherwise
Division by zero Panics, for / and % on every integer type. Java throws ArithmeticException, which ends the program where nothing catches it, as a panic does A branch on zero, then the runtime’s panic
Rounding Division truncates toward zero, and the remainder takes the dividend’s sign. floor_div and floor_mod are Java’s Math.floorDiv and Math.floorMod / and % after the checks
Tested
fn main() {
let max = i32.MAX
let min = i32.MIN
let minus_one = -1
println("{max + 1} {min / minus_one} {min % minus_one}")
println("{-7 / 2} {-7 % 2} {(-7).floor_div(2)} {(-7).floor_mod(2)}")
let byte: i8 = 127
println("{byte + 1}")
let n = 33
println("{1 << n} {-16 >> 2} {-16 >>> 28}")
}
  • An integer literal takes its type from where it’s used: let x: u8 = 200, wide + 1 where wide is an i64, or an argument of type u16. When nothing gives it a type, it’s an i32.
  • A suffix fixes the type: 5i64, 255u8, 3usize.
  • A literal must fit its type, or it’s a compile error. That includes hex: Java’s int literal 0x9E3779B9 is a bit pattern that doesn’t fit in an i32, so Calx writes it i32.wrap(0x9E3779B9u32).
  • A usize literal must fit in 32 bits, since usize is 32 bits on wasm32.
  • -128i8 is one literal, so the minimum of each type can be written directly.
Tested
fn main() {
let a: u8 = 200
let b: i64 = 1 << 40
println("{a} {b} {-128i8} {i32.wrap(0x9E3779B9u32)}")
}
Doesn't compile, on purpose
fn main() {
let _a: u8 = 256
error: 256 doesn't fit in u8 (0 to 255)
let _b: i32 = 0xFFFFFFFF
error: doesn't fit in i32
let _c = -1u32
error: u32 can't be negative
}

An expression made only of literals and constants is computed while compiling. If it overflows its type, that’s a compile error, even though the same operation wraps at run time: a constant that overflows is almost always a mistake. Write wrapping_add, wrapping_sub or wrapping_mul when wrapping is what you mean.

Doesn't compile, on purpose
fn main() {
let _a = i32.MAX + 1
error: this constant expression overflows i32
let b = i32.MAX.wrapping_add(1)
println("{b}")
}
  • Both sides of +, -, *, /, %, &, | and ^ have the same type. There’s no mixing an i32 with an i64 (conversions).
  • A shift’s count can be any integer type. A constant count must be from 0 to the width minus one, since Java masks x << 32 on an int to a shift by 0, which is never what was meant.
  • i8, i16, u8 and u16 don’t shift. Java promotes them to int first, and Calx makes that explicit: i32(b) << 2.
  • Unary - doesn’t apply to unsigned types. ~ is bitwise not, for integers only.
  • >>> on an unsigned type would be the same as >>, so it’s an error there.
Method Meaning
x.abs() The absolute value, for signed types. i32.MIN.abs() is i32.MIN, as in Java
x.min(y), x.max(y) The smaller or larger
x.wrapping_add(y), wrapping_sub, wrapping_mul The same as +, - and *, which also wrap. In a constant expression they say that wrapping is meant
x.floor_div(y), x.floor_mod(y) Java’s Math.floorDiv and Math.floorMod, for signed types. They panic on division by zero
x.rotate_left(n), x.rotate_right(n) The bits rotated by n, an i32, taken modulo the width, as Java’s Integer.rotateLeft and Long.rotateLeft do. Negative n rotates the other way

| x.checked_add(y), checked_sub, checked_mul | some of the result, or none if it doesn’t fit the type | | x.checked_div(y), checked_rem | none when y is 0. MIN.checked_div(-1) is none, since the result doesn’t fit; MIN.checked_rem(-1) is some(0), as Java’s % gives | | x.saturating_add(y), saturating_sub, saturating_mul | The result, clamped to the type’s range |

T.parse(text) reads an integer from a str or a String, giving an optional: none unless the whole text is one in range. It follows Java’s Integer.parseInt: an optional + or -, then one or more digits, with no spaces, _ or prefixes. Unsigned types take no -, not even before 0, as Integer.parseUnsignedInt.

Tested
fn main() {
println("{i32.parse("-42") ?? 0} {i32.parse("+7") ?? 0} {u8.parse("255") ?? 0}")
println("{i32.parse("2147483648") == none} {i32.parse(" 1") == none} {u32.parse("-0") == none}")
}

The digits are ASCII only. Java’s parseInt also takes other scripts’ digits, such as "١٢"; that’s a deliberate difference, since no protocol or file format sends them.

Java’s operators wrap, and so do Calx’s. Code that wants to notice an overflow says so: the checked_ methods return an optional, and the saturating_ methods stop at MIN or MAX. They work on every integer type, and compile to clang’s overflow builtins, a single flag test.

Tested
fn main() {
println("{i32.MAX.checked_add(1) == none} {i32.MAX.checked_add(-1) ?? 0}")
println("{7.checked_div(0) == none} {i32.MIN.checked_div(-1) == none} {i32.MIN.checked_rem(-1) ?? 9}")
println("{200u8.checked_add(56u8) == none} {0u32.checked_sub(1u32) == none}")
println("{i32.MAX.saturating_add(1)} {250u8.saturating_add(10u8)} {3u8.saturating_sub(10u8)}")
}