Djinn Lang

Numbers

Djinn provides fixed-width integer and floating-point types, native-width aliases, and per-type overflow behavior via type/literal suffixes.

Integer Types

Integers are iN (signed) and uN (unsigned) for widths 8, 16, 32, 64 and 128.

i8 a = 127;
u32 b = 4000000000;
i64 c = 800'000'000'000;

Number literals support _ and ' separators, scientific notation (1e9), hex (0xFF) and binary (0b1010) notation.

Native-Width Types

  • nint — signed integer with the target platform's pointer width (i64 on 64-bit targets, i32 on 32-bit). Ideal for sizes, indexes and FFI with C ptrdiff_t/intptr_t.
  • nfloat — the platform's C float (IEEE 754 single precision, f32).
  • ndouble — the platform's C double (IEEE 754 double precision, f64).
nint index = 0;
nfloat ratio = 1.5;
ndouble precise = 2.718281828;

Native types are display names: typeof and diagnostics show nint, and they interoperate freely with their fixed-width equivalents (i64, f32, f64).

Overflow Modes

Plain integer arithmetic wraps silently on overflow (C-style). Every integer type — and integer literals — accept a suffix that selects what happens when an operation overflows:

SuffixModeBehavior on overflow
wwrappedTwo's complement wrap (the default, made explicit)
ttrappedRuntime panic — aborts the program
ccheckedRaises the builtin Overflow error (requires a throws function)
ssaturatingClamps the result to MIN_VALUE/MAX_VALUE
i32 common = 123;
i32w wrapped = 121231234w;      // wraps like C
i32t trapped = 232134324t;      // panics on overflow
i32c checked = 232343c;         // throws Overflow
i32s saturation = 23498s;       // clamps to i32.MAX_VALUE

The mode is a property of the operation's operands: the left operand's mode wins, and the right one applies when the left has none. Conflicting explicit modes are a compile error.

i32w a = 1000w;
i32w b = a + 23;      // wrapping add (mode from `a`)
i32w c = 2000w + 3w;  // wrapping add (mode from the literals)
i32s d = 2000000000s;
i32s e = d + d;       // saturates at i32.MAX_VALUE = 2147483647

Checked overflow

The c mode raises the language-level builtin Overflow error through the standard error handling mechanism, so the enclosing function must declare throws:

i32 safeAdd(i32c a, i32c b) throws {
    return a + b; // sets the error flag instead of wrapping
}

i32 main() throws {
    i32 result = safeAdd(2147483647c, 1c) ?: -1; // try/fallback
    return result;
}

Compile-time behavior

Overflow rules also apply to literals and constexpr/consteval expressions:

  • An out-of-range literal is a compile error for t and c types
  • Saturating literals clamp at compile time (i32s a = 3000000000s; becomes 2147483647)
  • Wrapped literals keep the C-style truncation
i32t bad = 3000000000t;  // error: integer literal overflows 'i32t'
i32s clamped = 3000000000s; // ok: value is i32.MAX_VALUE
i32w wrapped = 3000000000w; // ok: wraps to -1294967296

What is covered

The +, -, *, /, % operators, unary negation (-x) and ++/-- all honor the overflow mode — including the INT_MIN / -1 edge case for division and remainder.

Non-Zero Types

The n suffix declares an integer type that cannot hold the value 0. The domain of iNn is MIN..-1 plus 1..MAX; the domain of uNn is 1..MAX. Non-zero types compile to the same representation as their base type — the guarantee is enforced by the compiler:

  • Assigning the literal 0 (including 0x0, 0b0) is a compile error
  • Implicit conversion from a plain integer type is rejected — the value might be zero; use an explicit cast, which is trusted and unchecked at runtime
  • Non-zero values convert implicitly to the plain type
  • Arithmetic results lose the guarantee (a - a is zero), so i32n + i32n produces i32
  • ++/-- are rejected on non-zero variables (the result may be zero), and a non-zero variable must be initialized when declared
u32n divisor = 10;        // ok
u32n bad = 0;             // error: integer literal 0 is not assignable to non-zero type 'u32n'

i32 plain = 5;
i32n fromPlain = plain;   // error: cannot implicitly convert 'i32' to 'i32n'
i32n trusted = (i32n)plain; // ok: explicit cast is trusted

i32n a = 5;
i32 sum = a + a;          // ok: result type is i32 (non-zero-ness dropped)
i32 back = a;             // ok: non-zero flows into the plain type freely

The suffix combines with overflow suffixes in either order: i32nt/i32tn is a trapped non-zero 32-bit signed integer.

Contracts

Non-zero types integrate with the contracts system in both directions:

  • Non-zero parameters are implicit require(p != 0) clauses. The callee checks the parameter at entry (a violation throws ContractViolation), and with compile-time error enforcement a call passing a constant 0 is rejected by the compiler (9007). Inside the body the parameter carries the guarantee, so divisions by it skip the zero-divisor check.
  • require(p != 0) upgrades a plain parameter to non-zero for the whole body: it can be assigned to i32n variables and used as a proven non-zero divisor. The explicit require is folded into the single non-zero entry check.
  • ensure(return != 0) upgrades the return type to non-zero for callers, regardless of declaration order, so i32n x = try f() ?: 1; compiles (functions with contracts are throwing, hence the try).
i32 safeDiv(i32n b) {
    return 100 / b;   // no zero-divisor check: b is contract-guaranteed non-zero
}

i32 legacyDiv(i32 b)
    require(b != 0)   // b is treated as i32n inside the body
{
    return 100 / b;
}

i32 five()
    ensure(return != 0)
{
    return 5;
}

i32 main() {
    i32n x = try five() ?: 1;  // ok: ensure proves the result non-zero
    safeDiv(0);       // compile error: passes zero to non-zero parameter 'b'
    return 0;
}

Division and remainder by a proven non-zero divisor (non-zero literal, i32n variable, contract-proven parameter, or a cast to a non-zero type) emit a plain sdiv/srem with no runtime zero check.

On this page