← Back to AILP Home

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 100 infer as int32. If you need a different width, always include the suffix — especially for int8 and int16 where 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 tbb types, which trap to an ERR sentinel value on overflow — they do not wrap. Use tbb when you need overflow safety; use int when 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 >> N uses lshr (zero-fills high bits); int >> N uses ashr (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] int1 range is {−1, 0} — it is a signed 1-bit type, not {0, 1}. This makes it distinct from uint1, 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 int128 or larger. For GCC/Clang interop, use __int128_t for int128. For int256+, 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)

Related