U32 += u32 - 1 is not same as C language

I roughly understand the OP’s question. It stems from a misunderstanding of operator precedence. The OP mistakenly assumes that += has higher precedence than + and -. However, + and - actually have higher precedence than += and -=.

This leads the OP, assuming the misunderstanding of += operator precedence, to misattribute the difference in zig’s behavior from C, assuming that the difference lies in operator precedence. However, the real difference is that zig doesn’t use wraparound.

3 Likes

This is correct, Zig and C both place += as lower and + as higher.

This is Zig’s precendence table, and this is C’s precedence table, which I believe are identical, modulo differences in which operators exist in each language.

Edit: nope, forgot that Zig made bitwise operators not do the crazy thing which messes up your code when combined with comparators. But not relevant to the thread.

1 Like

I just realized how stupi I was. I totaly understand now after reading yours explaing and did some math experiments.

Thanks a lot!

7 Likes

I suppose in this example you see two of zigs explicit language choices interacting in a interesting way.

  1. Integers check against integer overflow: Documentation - The Zig Programming Language if you want a different behaviour checkout the Builtin Overflow Functions or Wrapping Operations
  2. Aggressive compile-time evaluation.

To fix 1. you should keep this in mind and decide what operations you actually want and if you really want wrap around you have to opt-in.

About 2. I suggest to consider this a strict improvement that the compiler tries very hard to optimize away constant values. And that it told you with a compile error that subtracting -1 from 0 is not gone work without wrapping around.

1 Like

This help exploring underneath prombles makes me prefer Zig’s explicit MORE!

2 Likes

It’s quite nice once you get the hang of it!

For instance, wraparound arithmetic is essential for hash functions, and those can be translated directly from another language simply by using the % variants of the arithmetic operators.

Saturation is frustratingly difficult to express without saturating operators, and when you need it, you really need it.

Ultimately, the compiler can’t tell if an overflow is a mistake, or intended behavior. They mean different things, so Zig forces code to be explicit about it, and checks that the contract is observed in safe modes.

There’s a similar thing, where languages disagree on the correct value for signed integer division, when the divisor is negative. So in Zig you have to use one of several builtins which make it clear which one is meant.

I do wish we had /% and /- instead of having to call them @divTrunc and @divFloor though. The modulus equivalents aren’t as bad, but it’s awkward-looking doing division with a function call, and it messes up precedence, end up putting things ‘inside’ which seem like they should be ‘outside’.

Still better than assuming it’s going to do one thing, then having it do something subtly different.

4 Likes

Yes, I agree. It feels a bit strange at first, but once you get used to it and realize how often these nuances are actually important in your own code, it becomes something that is much appreciated. It just takes “unlearning” some of C’s quirks that we have all become accustomed to and deem as “normal”, when they are actually quite serious footguns that have been the source of countless (often difficult to find) bugs.

2 Likes

There still is 0 - 1 overflow: Compiler Explorer

1 Like

Here, max_u32 + some should also be saturating. Also, you tried to use +|= on initial value, which is compiler error. The line you meant is
const saturated = max_u32 +| some;

a +%= b -% 1;
use +% and -% everywhere to get behavior consistent with C

2 Likes

With the caveat that wrapping is also well-defined for signed integers in Zig, which is technically untrue for C.

2 Likes

That is incorrect. In standard C, a += expr; is exactly equivalent to a = a + (expr);

So in this case it becomes a = a + (b - 1); not (a + b) - 1. This equivalence holds in all C standards (C90–C23).

Edit: Your example only works because in C, arithmetic with unsigned ints always runs modulo 2^n. Negative values ​​are neatly folded back into the numeric range.

1 Like