Signed Integers — int
Widths: int1, int2, int4, int8, int16, int32, int64, int128, int256, int512, int1024, int2048, int4096
Overview
Nitpick provides two's complement signed integers in powers of 2 from 1-bit to 4096-bit.
int32 (alias i32) is the default integer type — bare literals like 100 infer as int32.
Signed integers behave like signed integers in C, Rust, or any other systems language: arithmetic wraps
on overflow (two's complement), right-shift is arithmetic (sign-extending), and there is no automatic
error propagation. If you need error propagation, use the tbb types instead.
Width Table
| Type | Bytes | Range | Alias | Notes |
|---|---|---|---|---|
| int1 | 1 | −1 to 0 | i1 | Sign bit only |
| int2 | 1 | −2 to 1 | i2 | |
| int4 | 1 | −8 to 7 | i4 | |
| int8 | 1 | −128 to 127 | i8 | abs(−128) is undefined |
| int16 | 2 | −32768 to 32767 | i16 | |
| int32 | 4 | −2^31 to 2^31−1 | i32 | Default. Wraps on overflow |
| int64 | 8 | −2^63 to 2^63−1 | i64 | |
| int128 | 16 | −2^127 to 2^127−1 | i128 | LBIM — see below |
| int256 | 32 | −2^255 to 2^255−1 | i256 | LBIM |
| int512 | 64 | −2^511 to 2^511−1 | i512 | LBIM |
| int1024 | 128 | −2^1023 to 2^1023−1 | i1024 | LBIM |
| int2048 | 256 | −2^2047 to 2^2047−1 | i2048 | LBIM |
| int4096 | 512 | −2^4095 to 2^4095−1 | i4096 | LBIM |
Declaration
int32:x = 42; // explicit type; i32 suffix works too
int64:big = 9999999i64; // suffix selects width
int128:huge = 99999999999999999999i128; // suffix required for large literals
int8:neg = -128i8; // negative literals are valid for signed types
Literal Suffixes
Every supported signed integer width has a corresponding literal suffix:
i1, i2, i4, i8, i16, i32, i64, i128, i256, i512, i1024, i2048, i4096.
int8:a = -1i8;
int16:b = -32768i16;
int32:c = -2147483648i32; // INT32_MIN
int64:d = -9223372036854775808i64; // INT64_MIN
[!WARNING] Bare literals like
100infer asint32. If you need a different width, always include the suffix — especially forint8andint16where the range is narrow.
Arithmetic
int32:a = 10;
int32:b = 3;
int32:sum = (a + b); // 13
int32:diff = (a - b); // 7
int32:prod = (a * b); // 30
int32:quot = (a / b); // 3 (truncates toward zero, C semantics)
int32:rem = (a % b); // 1 (sign of dividend)
a++; // 11
b--; // 2
Division truncates toward zero: (−7 / 3) = −2, (7 / −3) = −2.
Modulo sign follows the dividend: (−7 % 3) = −1, (7 % −3) = 1.
Overflow Behavior
Signed arithmetic wraps silently via two's complement. When a computation exceeds the maximum value, it wraps to the minimum; when it goes below the minimum, it wraps to the maximum.
int32:max = 2147483647i32;
int32:wrapped_add = (max + 1i32); // == -2147483648i32 (INT32_MIN)
int32:min = -2147483648i32;
int32:wrapped_sub = (min - 1i32); // == 2147483647i32 (INT32_MAX)
int8:max8 = 127i8;
int8:wrapped8 = (max8 + 1i8); // == -128i8
[!NOTE] This differs from
tbbtypes, which trap to anERRsentinel value on overflow — they do not wrap. Usetbbwhen you need overflow safety; useintwhen you want C-style wrapping semantics.
Bitwise Operations
Signed integers support all bitwise operators. The key distinction from unsigned types is that right-shift is arithmetic (sign-extending), not logical (zero-filling).
int32:a = 65280i32; // 0x0000FF00
int32:b = 4080i32; // 0x00000FF0
int32:and_result = (a & b); // 0x00000F00 = 3840
int32:or_result = (a | b); // 0x0000FFF0 = 65520
int32:xor_result = (a ^ b); // 0x0000F0F0 = 61680
int32:not_result = (~a); // -65281 (bitwise NOT, all bits inverted)
int32:shl = (1i32 << 30i32); // 1073741824
Right-shift: Arithmetic (AShr), not Logical
For signed integers, >> emits LLVM ashr (arithmetic shift right), which preserves the sign bit
by filling vacated high bits with copies of the sign bit:
int8:neg128 = -128i8;
int8:shifted = (neg128 >> 1i8); // -64 (0x80 >> 1 = 0xC0, NOT 0x40)
int32:neg = -1i32;
int32:all1s = (neg >> 15i32); // -1 (all bits stay set)
int64:min64 = -9223372036854775808i64;
int64:half = (min64 >> 1i64); // -4611686018427387904
[!IMPORTANT] This is the critical semantic difference between signed and unsigned right-shift.
uint >> Nuseslshr(zero-fills high bits);int >> Nusesashr(sign-fills high bits). Mixing the two produces different results for values with the high bit set.
Sub-byte Signed Integer Semantics
int1, int2, and int4 are stored in an 8-bit LLVM type but the compiler enforces their
narrower ranges through i1/i2/i4 LLVM integer types:
| Type | Range | Notes |
|---|---|---|
int1 |
−1 to 0 | Only valid values: −1i1 or 0i1. Range does not include 1. |
int2 |
−2 to 1 | Two's complement 2-bit: {−2, −1, 0, 1} |
int4 |
−8 to 7 | Two's complement 4-bit: {−8, …, 7} |
int1:a = 0i1;
int1:b = -1i1;
// Note: int1 range is {-1, 0} — NOT {0, 1} like uint1
// int1 + int1 arithmetic wraps within the 2-value range
int2:c = 1i2;
int2:d = -1i2;
int2:sum = (c + d); // 0
int2:diff = (c - d); // wraps: 1 - (-1) = 2 → -2 in i2
int4:e = 7i4;
int4:f = -3i4;
int4:prod = (e + f); // 4
[!NOTE]
int1range is{−1, 0}— it is a signed 1-bit type, not{0, 1}. This makes it distinct fromuint1, which covers{0, 1}. Both are valid; pick whichever range matches your use case.
Width Conversion
int64:wide = a; // widening: always safe, sign-extends
int32:narrow = @cast<int32>(big); // narrowing: truncates high bits
uint32:as_uint = @cast<uint32>(a); // cross-sign: bit-reinterpretation
Widening from intN to intM (M > N) is sign-extending — the sign bit is replicated into the new
high bits. This means −1i8 widens to −1i32, not to 255i32.
Narrowing truncates the high bits: 256i64 → 0i8 (lower 8 bits), −1i64 → −1i8.
[!NOTE] Implicit conversion between signed (
int) and unsigned (uint) is forbidden. You must use an explicit cast (e.g.,@cast<uint32>(my_int)). See uint.md.
C FFI Mapping
When interfacing with C code via extern blocks, Nitpick signed integers map to standard C types:
| Nitpick | C Type | Size |
|---|---|---|
int8 |
int8_t / signed char |
1 byte |
int16 |
int16_t / short |
2 bytes |
int32 |
int32_t / int |
4 bytes |
int64 |
int64_t / long long |
8 bytes |
[!NOTE] Standard C has no equivalent for
int128or larger. For GCC/Clang interop, use__int128_tforint128. Forint256+, pass as a pointer to a struct at the ABI level.
LBIM Types (≥128 bit)
Widths int128 through int4096 use the Limb-Based Integral Model:
- Stored as arrays of 64-bit limbs
- Slower than int64 — use only when necessary
- Division and modulo call runtime library functions (npk_lbim_sdivN / npk_lbim_smodN)
- Comparisons call npk_lbim_scmpN (signed) — not the unsigned version
- Type suffix required for literals: 42i128, 99i1024
- Use cases: cryptography, post-quantum key material, UUID storage, high-precision financial
ABI Notes
| Width | ABI Passing |
|---|---|
| int1–int64 | Register (zero/sign-extended to i8 minimum) |
| int128+ | Pointer (sret/byval) |