Skip to content

Integer Literals

# Decimal integers
x = 0
y = 42
z = -10
large = 1000000

# Underscores for readability (optional)
million = 1_000_000
billion = 1_000_000_000

Leading zeros are digits (C#'s rule): 007 is the decimal integer 7 and 0_1 is 1. Python refuses both (leading zeros in decimal integer literals are not permitted); an octal literal is spelled 0o7 in both languages (deviation leading-zero-decimal-integer in docs/deviations.yaml).

def main():
    print(007, 0_1, 0o7)   # 7 1 7

Type Inference

  • Integer literals without a suffix are inferred as int (32-bit, System.Int32) by default
  • If the literal value exceeds Int32.MaxValue (2,147,483,647), it is automatically promoted to long (64-bit, System.Int64)
  • If the literal value exceeds Int64.MaxValue (9,223,372,036,854,775,807), it is a compile-time error
  • Suffix notation for explicit sizing (optional):
  • L or l for int64 (System.Int64): 42L
  • u or U for uint32 (System.UInt32): 42u
  • ul or UL for uint64 (System.UInt64): 42ul

The integer suffixes work on every base — hexadecimal (0x), binary (0b) and octal (0o) literals take them exactly as decimal ones do, with or without digit separators:

def main():
    mult = 0x41C64E6Du        # uint32
    mask = 0xFFFF_FFFFL       # int64
    flags = 0b1010_0101u      # uint32
    mode = 0o755ul            # uint64
    print(mult, mask, flags, mode)   # 1103515245 4294967295 165 493

A hex digit is never read as a suffix: a–f and A–F are digits, so 0xFFf is 0xFFF (4095) and 0x1D is 29. The float suffixes f, d and m (see Float Literals) apply to decimal literals only; on a binary or octal literal, and m on a hex literal, they are refused (SPY0023), as in C#:

# x = 0b11f                 # ERROR (SPY0023): Invalid numeric suffix: f (a binary literal accepts only the integer suffixes u, l and ul)
# y = 0xFFm                 # ERROR (SPY0023): Invalid numeric suffix: m (a hexadecimal literal accepts only the integer suffixes u, l and ul)

Note: Like C#, there are no literal suffixes for int16, uint16, uint8, or int8. Use type annotations or explicit casts:

# Type annotation
s: int16 = 42
b: uint8 = 255
sb: int8 = -128

# Explicit casting (with the `as!` operator)
s = 42 as! int16
b = 255 as! uint8

Overflow Promotion

When an integer literal exceeds the range of int (32-bit), the compiler automatically promotes it to the next wider type:

Value Range Inferred Type
-2,147,483,648 to 2,147,483,647 int (System.Int32)
Outside int range int64 (System.Int64)
small = 42                    # int (fits in 32-bit)
large = 3_000_000_000         # int64 (exceeds int range, auto-promoted)
explicit = 42L                # int64 (explicit suffix)

Literals in a mixed-type expression

A suffixed literal carries its own type: 5u is uint32 and 5ul is uint64, exactly as a variable of that type would be. An unsuffixed literal too large for int is int64.

# a: int32 = 5u             # ERROR (SPY0220): Cannot assign type 'uint32' to variable of type 'int32'
# b: int32 = 4294967296     # ERROR (SPY0220): Cannot assign type 'int64' to variable of type 'int32'

An unsuffixed literal converts to an unsigned type by its value, wherever it is stored — an annotated variable, an argument, a return, a const — as C#'s uint m = 0xFFFFFFFF; does. Only a literal does: a suffixed or declared int64 constant converts to uint64 alone (C# §10.2.11).

mask: uint32 = 0xFFFFFFFF    # 4294967295 — the literal's value fits uint32
top: uint32 = 2147483648     # 2147483648
print(mask, top)
# big: uint32 = 4294967296   # ERROR (SPY0220): Cannot assign type 'int64' to variable of type 'uint32'
# n: uint8 = 5L              # ERROR (SPY0220): Cannot assign type 'int64' to variable of type 'uint8'

When such a literal meets an operand of another type, the pair goes through C#'s binary numeric promotion (§12.4.7) — the same table +, < and & all use, tabulated in Numeric Type Promotion. A uint64 operand against any signed operand has no common type and is refused (SPY0222).

A constant operand converts first. When exactly one operand is an integer constant whose value fits the other operand's type, the constant converts to that type before promotion (C# §10.2.11), so the operator sees a same-type pair and the result keeps the variable's type:

b: uint32 = 5
c: uint32 = b + 1        # 6 — the constant 1 converts to uint32; the result is uint32

u: uint64 = 5
s: uint64 = u + 4294967296   # 4294967301 — an int64-typed constant that fits uint64 converts too

a: uint64 = 5
# print(a + (-1))        # ERROR (SPY0222): Type 'uint64' does not support operator '+'
#                        #   with operand of type 'int32' — -1 has no uint64 value

The constant's own recorded type does not change (hover on 1 still shows int); only the operator's effective operand types do. A constant whose value does not fit stays its own type and the ordinary promotion applies, so uint32 + 4294967296 is int64.

Implementation - ✅ Native - Direct mapping to C# integer literals.