Integers
Integer arithmetic in Calx matches Java's bit for bit - overflow, division, shifts and literals.
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 |
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}")}-2147483648 -2147483648 0 -3 -1 -4 1 -128 2 -4 15
Literals
Section titled “Literals”- An integer literal takes its type from where it’s used:
let x: u8 = 200,wide + 1wherewideis ani64, or an argument of typeu16. When nothing gives it a type, it’s ani32. - 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
intliteral0x9E3779B9is a bit pattern that doesn’t fit in ani32, so Calx writes iti32.wrap(0x9E3779B9u32). - A
usizeliteral must fit in 32 bits, sinceusizeis 32 bits onwasm32. -128i8is one literal, so the minimum of each type can be written directly.
fn main() { let a: u8 = 200 let b: i64 = 1 << 40 println("{a} {b} {-128i8} {i32.wrap(0x9E3779B9u32)}")}200 1099511627776 -128 -1640531527
fn main() { let _a: u8 = 256error: 256 doesn't fit in u8 (0 to 255) let _b: i32 = 0xFFFFFFFFerror: doesn't fit in i32 let _c = -1u32error: u32 can't be negative}Constant expressions
Section titled “Constant expressions”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.
fn main() { let _a = i32.MAX + 1error: this constant expression overflows i32 let b = i32.MAX.wrapping_add(1) println("{b}")}Operators
Section titled “Operators”- Both sides of
+,-,*,/,%,&,|and^have the same type. There’s no mixing ani32with ani64(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 << 32on anintto a shift by 0, which is never what was meant. i8,i16,u8andu16don’t shift. Java promotes them tointfirst, 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.
Methods
Section titled “Methods”| 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 |
Parsing
Section titled “Parsing”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.
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}")}-42 7 255 true true true
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.
Checked and saturating arithmetic
Section titled “Checked and saturating arithmetic”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.
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)}")}true 2147483646 true true 0 true true 2147483647 255 0