Overflow check is done for each subexpression

When adding integers, it looks llike the compiler will check for overflow at each substep of the evaluation. Here is a code sample from my project:

try writer.print("↑ {}", .{ip - offset + 1});

All values are of type usize.
This may cause an overflow and it triggers in debug mode with example values of ip: 4, offset: 5 . However the overall expression evaluates to zero, I’d argue their is no overflow overall.

I fixed my code like this:

try writer.print("↑ {}", .{(ip + 1) - offset});

But it might not work all the time. Is this a limitation of the zig langage, a bug in the compiler, or me having a smol brain ?

The end result being 0 doesn’t change the fact that an overflow happened, zig defines overflows for normal maths operations as illegal behaviour which gets checked in safe build modes.

if you want to allow overflows, use the wrapping operators (+%, -%, *%), these are better as it communicates to zig what you want so it can ensure consistent behaviour across targets, however unlikely it is that a targets overflow behaviour is different

2 Likes

Allowing overflows to happen is not my intent though, this mean I have to resort to check for it explictely. I believe I have to use wider integer (sub)results and check for the high bits if I want to be efficient. As in:

const res: u11 = someu8 + someu8 + someu8 + someu8;
if(res & 0b111_0000_0000 > 0) @panic("yikes !");

It depends on the equation, the known constraints of the numbers, what assumptions you’re willing to make and the build target.

In order of what I think is efficient:
the best solution is to re-arrange the equation to avoid the overflow, not sure why you think (ip + 1) - offset might not work.

I would then see if exploiting overflow is possible, based on known constraints and assumptions I could make.

Then I’d turn to wider integers.
FYI, assuming you’re building for a 64-bit system, a u65 will 16 bytes big due to alignment.
Also, you only need a u10 for 4 u8s

I’d swap the last two if the integer size already being used is small,

I’d say Zig is right to check overflow in each expression step because an overflow in the middle of an expression may break the end result - basically any place which expects that x+1 > x and x-1 < x. For instance let’s say you have an expression like:

const i = (x + 1) * 2;

…with x being an unsigned integer. It’s safe to assume that (i != 0) && (i > x). But if (x + 1) overflows, i will suddenly be zero. If only the end result would be checked for overflow, the problem wouldn’t be caught.

3 Likes

Every command in source code maps to a specific series of machine code instructions. There is no machine that I know of that can do an addition and a subtraction in one instruction. The expression would result in an overflow at the machine level. Whether that would lead to incorrect results would depend on the target machine.
It is the programmer responsibility to order the instructions in a way that leads to valid intermediate results, because the compiler can’t do it for you in the general case.

3 Likes