iirc the uint16s are promoted to signed ints before the multiplication
I just checked what the Rust reference (not the book) actually tells us about overflowing integers in release mode. Nothing about release-mode here:
Integer operators will panic when they overflow when compiled in debug mode. The
-C overflow-checkscompiler flags can be used to control this more directly. The following things are considered to be overflow:
-create a value greater than the maximum value, or less than the minimum value that can be stored. This includes unary
-on the smallest value of any signed integer type.
%, where the left-hand argument is the smallest integer of a signed integer type and the right-hand argument is
-1. These checks occur even when
-C overflow-checksis disabled, for legacy reasons.
>>where the right-hand argument is greater than or equal to the number of bits in the type of the left-hand argument, or is negative.
But I found something here:
In the case of implicitly-wrapped overflow, implementations must provide well-defined (even if still considered erroneous) results by using two's complement overflow conventions.
I assume "implementations" here means "implementations of a Rust compiler".
Agreed that it should say "wrapping" or "modular", rather than "two's complement". It doesn't matter here how the values are represented bitwise, what matters is what values you get after overflow. And it should say that in the Overflow section.
I think it's fine to say "two's complement" if (also) signed numbers are involved. It just confused me to read about it when the example is
Yep. You might think you would still get 2.5 billion in practice after the result is cast to
uint32_t but no.
What's printed doesn't even fit in 32 bits!
Which shows that undefined behavior isn't just unspecified behavior, in existing compilers, you can get total mess out of a simple example like this which doesn't even have any branches.
Depends on the
int size of the platform you're using, because of integer promotion in arithmetic.
Yes, but integer promotion is evil.
-C overflow-checks is the relevent part for the default release profile settings. Note that profiles are a Cargo concept, not a Rust concept.
I'd say wrapping. The choices around, say, shifts being periodic, I wouldn't necessarily associate with... much else at all, really.
That's just because of binary. The simplest way to do shift hardware for an N-bit integer is to only look at the lowest log₂(N) bits of the shift amount.
And if you're on an unusual chip that does something more complicated, masking it to get the specified behaviour is cheap, whereas all the comparisons to get more complicated behaviour on something like x86 are much harder.
Meh only 2 more assembly instructions. None if the shift is a constant, or if the compiler can prove it's small. If it's not a constant, I often end up having to do this anyway, because 32 is sometimes a possible input, while having to remember that << breaks on 32.
One scenario where I have had this issue is in implementing:
fn low_bits(n: u32) -> u32
n can be anything from 0 to 32 inclusive.
Rust calls an intrinsic. The traditional circuit implementation would be to add the two's complement. That's one of its major appeals.
Fine, you win
So in that case hardware does use two's complement on subtraction of two unsigned integers (apparently).