Numbers are at the heart of almost every program. This page covers the integer types Loft provides, the arithmetic and bitwise operations you can perform on them, how to convert between numbers and text, and what Loft does when something goes wrong — such as dividing by zero.
The default number type is integer: a 64-bit signed whole number, the same as i64 in Rust. It can hold values from about −9 quintillion to +9 quintillion (roughly ±9.2 × 10^18), so everyday counts and very large numbers both fit in the one type — there is no separate long. For decimal (fractional) numbers, see the Float page.
fn main() {
Converting between numbers and text
Wrapping a value in {...} inside a string formats it as text. Going the other way, as integer parses a text value into a number. If the text cannot be parsed, the result is null — not a crash.
v = 4;
assert("{v}" == "4", "Format integer as text");
assert("123" as integer? == 123, "Parse text to integer");
assert(!("abc" as integer?), "Unparseable text gives null");
Arithmetic and operator precedence
Loft follows standard mathematical precedence: * and / before + and -. Bitwise operators (<<, &, ^) have their own precedence — when mixing them with arithmetic, parentheses make your intent clear and avoid surprises. Note: ^ is XOR, not exponentiation. Use pow(base, exp) for powers.
assert(1 + 2 * 4 == 9, "Multiplication before addition");
assert(1 + 2 << 2 == 12, "Shift: (1+2) << 2 = 12");
assert(0x0a8 & 15 == 8, "Bitwise AND masks low 4 bits");
assert(42 ^ 0b111111 == 21, "Bitwise XOR");
assert(105 % 100 == 5, "Modulus (remainder)");
assert(pow(2.0, 3.0) == 8.0, "pow() for exponentiation");
abs() returns the absolute value — the distance from zero, always positive.
assert(1 + abs(-2) == 3, "abs(-2) == 2");
Division by zero — produces null and keeps running
Most languages crash on division by zero. Loft does NOT: a divide (or modulo) by zero is uncomputable, so it produces null and execution CONTINUES — the spreadsheet model, where one bad cell shows an error but the rest still recalculate. This holds the same everywhere (development, test, and production) — one bad calculation never halts the run.
- Undefended (bare
1 / 0): you get null, and at this unguarded site loft also reports a warning so the divide-by-zero is not invisible. - Defended (
1 / 0 ?? fallback, or a followingif x != null):??supplies a non-null fallback and the warning is suppressed — you have explicitly handled the case.
a = 2 * 2;
a -= 4;
a is now 0
assert(!(12 / a ?? null), "Division by zero gives null when defended with `?? null`");
Warning when you write a literal zero divisor
When the divisor is a literal 0 written directly in your source code, loft warns you while reading your code (before running it), because that is almost certainly a mistake: n / 0 // warning: Division by constant zero n % 0 // warning: Modulo by constant zero Use a variable (like a above) when you intentionally want null-on-zero division without a warning.
Embedding integers in text
assert("a{12}b" == "a12b", "Integer in format string");
A full expression can appear inside {...}, not just a variable name.
assert("a{1 + 2 * 3}b" == "a7b", "Expression in format string");
Number format specifiers
After a : inside {...} you can control how a number is displayed: #x — hexadecimal with 0x prefix o — octal b — binary + — always show a sign (+ or -) N — minimum field width (space-padded on the left) 0N — minimum field width (zero-padded on the left)
assert("a{1+2+32:#x}b" == "a0x23b", "Hex format with 0x prefix");
assert("{12:o}" == "14", "Octal");
assert("{12:+4}" == " +12", "Sign and width");
assert("{1:03}" == "001", "Zero-padded width");
assert("{42:b}" == "101010", "Binary");
Hexadecimal literals in source code accept both lower and upper case digits.
assert(0xff == 255, "Lowercase hex literal");
assert(0xFF == 255, "Uppercase hex literal");
assert(0x2A == 42, "Uppercase hex digit");
Large integers
Because integer is 64-bit, values far past the old 2-billion limit work directly — no separate type is needed. Arithmetic, comparisons, and the format specifiers above all behave the same at these magnitudes.
big = 1000000000 * 5;
assert(big == 5000000000, "Integers well past 2 billion");
assert("{big}" == "5000000000", "Large integer formatted as text");
Common pitfall: integer overflow
A calculation whose result is too large to represent overflows — and in loft an overflow is uncomputable, so the result is null. It never wraps to a silently-wrong number, and it never crashes; execution continues with the null, exactly like divide-by-zero above. Check the result for null (or keep intermediate values in range) when multiplying or summing very large numbers.
}