Skip to content

Integer Overflow

An integer type can store only a fixed range of values.

An integer type can store only a fixed range of values.

For example:

TypeRange
u80 to 255
i8-128 to 127
u160 to 65535

When a calculation produces a value outside the range of the type, overflow occurs.

In Zig, ordinary arithmetic checks for overflow in safe build modes.

const std = @import("std");

pub fn main() void {
    var x: u8 = 255;

    x += 1;

    std.debug.print("{d}\n", .{x});
}

The value 256 does not fit in u8.

In Debug mode, Zig stops the program:

panic: integer overflow

This behavior is deliberate.

Silent overflow is a common source of bugs in systems programs. Zig requires the programmer to choose overflow behavior explicitly.

If wrapping arithmetic is wanted, use wrapping operators.

const std = @import("std");

pub fn main() void {
    var x: u8 = 255;

    x +%= 1;

    std.debug.print("{d}\n", .{x});
}

The result is:

0

The value wraps around modulo 256.

Wrapping operators exist for the main arithmetic operations.

OperatorMeaning
+%Wrapping addition
-%Wrapping subtraction
*%Wrapping multiplication

Assignment forms also exist:

OperatorMeaning
+%=Wrapping addition assignment
-%=Wrapping subtraction assignment
*%=Wrapping multiplication assignment

Signed integers can also overflow.

const std = @import("std");

pub fn main() void {
    var x: i8 = 127;

    x += 1;

    std.debug.print("{d}\n", .{x});
}

127 is the largest i8.

Adding 1 overflows.

Wrapping signed integers uses two’s complement behavior.

const std = @import("std");

pub fn main() void {
    var x: i8 = 127;

    x +%= 1;

    std.debug.print("{d}\n", .{x});
}

The result is:

-128

Sometimes overflow should be detected directly instead of trapping or wrapping.

Zig provides operations that return both the result and an overflow flag.

const std = @import("std");

pub fn main() void {
    const result = @addWithOverflow(250, 10);

    const value = result[0];
    const overflow = result[1];

    std.debug.print("{d} {d}\n", .{
        value,
        overflow,
    });
}

The result is:

4 1

The addition wrapped, and the overflow flag is set.

The builtins are:

BuiltinMeaning
@addWithOverflowAddition with overflow flag
@subWithOverflowSubtraction with overflow flag
@mulWithOverflowMultiplication with overflow flag
@shlWithOverflowShift-left with overflow flag

These are useful in parsers, cryptography, allocators, and arithmetic libraries.

Integer overflow matters because the compiler uses arithmetic assumptions during optimization.

Consider:

if (x + 1 > x) {
    // ...
}

For ordinary checked arithmetic, the compiler may assume the expression behaves mathematically when overflow is impossible.

When wrapping behavior is required, use explicit wrapping operators so the meaning is clear.

Shift operations are also checked.

const std = @import("std");

pub fn main() void {
    const x: u8 = 1;

    const y = x << 8;

    std.debug.print("{d}\n", .{y});
}

A u8 has only eight bits. Shifting by eight exceeds the bit width.

In safe modes, this is a panic.

Exact-width integer types make overflow rules predictable.

const a: u32 = 1000;
const b: i16 = -5;

The size and signedness are always explicit.

Zig avoids implicit integer widening and narrowing in many situations.

const a: u8 = 10;
const b: u16 = 20;

// error
const c = a + b;

The compiler requires an explicit conversion.

const c = @as(u16, a) + b;

This prevents accidental loss of range or sign.

Integer overflow is sometimes useful.

Hash functions often rely on wrapping arithmetic.

hash = hash *% 33 +% byte;

Counters and indexes usually should not overflow.

count += 1;

The ordinary operator expresses the safer intent.

The general rule is simple:

SituationPreferred operation
Overflow is a bug+, -, *
Overflow is intended+%, -%, *%
Overflow must be detected@addWithOverflow

The language forces the choice into the source code instead of hiding it in compiler rules.

Exercise 19-13. Write a program that overflows a u8 with +=.

Exercise 19-14. Rewrite Exercise 19-13 with +%=.

Exercise 19-15. Use @addWithOverflow to add two u16 values and print both the result and overflow flag.

Exercise 19-16. Write a simple hash loop that uses wrapping multiplication.