Consider the following Zig program:
{% c-block language="zig" %}
const std = @import("std");
pub fn main() void {
var x: u16 = 500;
var y: u16 = 500;
var z: u32 = x * y;
std.debug.print("{d}\n", .{z});
}
{% c-block-end %}
Run it with {% c-line %}zig run overflow.zig{% c-line-end %}:
{% c-block language="console" %}
thread 21769477 panic: integer overflow
/Users/ehaas/bugs/test.zig:5:20: 0x105008c97 in main (test)
var z: u32 = x * y;
^
/Users/ehaas/source/zig/build/lib/zig/std/start.zig:335:22: 0x105008f1d in std.start.main (test)
root.main();
^
???:?:?: 0x7fff205aa620 in ??? (???)
???:?:?: 0x0 in ??? (???)
[1] 98894 abort zig run test.zig
{% c-block-end %}
{% c-line %}500 * 500{% c-line-end %} is only {% c-line %}250,000{% c-line-end %} - so what happened? In Zig, the multiplication operator invokes Peer Type Resolution on its operands. In this case, the operands are both {%c-line %}u16{% c-line-end%}, so a 16-bit multiply will be performed, resulting in a value of {% c-line %}250000{% c-line-end%}, which is larger than the maximum {% c-line %}u16{% c-line-end%} value of {% c-line %}65535{% c-line-end%} - thus integer overflow occurs, which causes a panic in safety-checked build modes.
So how do we deal with this? There are least 3 ways, and it depends on what you're trying to do:
1. Use wrapping arithmetic with the {% c-line %}*%{% c-line-end %} operator (one mnemonic for remembering it is that this combines multiply ({% c-line %}*{% c-line-end %}) with modulus ({% c-line %}%{% c-line-end %}):
{% c-block language="zig" %}
const std = @import("std");
pub fn main() void {
@setRuntimeSafety(false);
var x: u16 = 500;
var y: u16 = 500;
var z: u32 = x *% y;
std.debug.print("{d}\n", .{z});
}
{% c-block-end %}
This will perform wrapping 16-bit arithmetic, and then coerce the result to 32 bits. In this case, {% c-line %}500*500{% c-line-end %} produces a 16-bit result of {% c-line %}1101000010010000{% c-line-end %} which is then extended to the 32-bit result {% c-line %}00000000000000001101000010010000{% c-line-end %}, or {% c-line %}53392{% c-line-end %}. Note that there is no way to tell if overflow occurred, since we explicitly asked for wrapping arithmetic.
2. {% c-line%}Use @mulWithOverflow{% c-line-end %}
{% c-block language="zig" %}
const std = @import("std");
pub fn main() void {
var x: u16 = 500;
var y: u16 = 500;
var z: u16 = undefined;
if (@mulWithOverflow(u16, x, y, &z)) {
std.debug.print("Oops! we had an overflow: {d}\n", .{z});
} else {
std.debug.print("{d}\n", .{z});
}
}
{% c-block-end %}
This will perform a 16-bit multiply. If there is no overflow, it will return {% c-line %}false{% c-line-end %} and store the result in {% c-line %}z{% c-line-end %}. If there is an overflow, it will return {% c-line %}true{% c-line-end %} and store the overflowed bits in {% c-line %}z{% c-line-end %}. Output: {% c-line %}Oops! we had an overflow: 53392{% c-line-end %}.
3. Cast an operand to {% c-line %}u32{% c-line-end %}:
{% c-block language="zig" %}
const std = @import("std");
pub fn main() void {
var x: u16 = 500;
var y: u16 = 500;
var z: u32 = @as(u32, x) * y;
std.debug.print("{d}\n", .{z});
}
{% c-block-end %}
Casting an operand to {% c-line %}u32{% c-line-end%} will cause Peer Type Resolution to resolve both arguments to {% c-line %}u32{% c-line-end %}, and produce a final result value of {% c-line %}250000{% c-line-end %}