C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Signed Overflow

From: connor horman <chorman64_at_[hidden]>
Date: Thu, 4 Sep 2025 13:20:12 -0400
On Thu, 4 Sept 2025 at 12:58, Julien Villemure-Fréchette via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> > Why? Because it's the choice that doesn't result in information loss
> when overflow occurs.
>
> AFAIK, arithmetic overflow is exactly means that bits of information were
> lost during the calculation. Furthermore, for the wrap around behavior, the
> lost bits are the most significant ones, so the result of a wrapping signed
> overflow on addition or subtraction couldn't be more erroneous than any
> other possible behavior: the distance from the mathematical result is
> exactly 2^N. For multiplication, wrapping signed overflow could happen at
> any intermediate addition so the distance could go up to somewhere near
> 2^(2N-1). The best possible way to minimize the distance between the
> mathematical and the result of computation would be to drop the low order
> bits instead of the high order bits, which is what saturation arithmetic
> does.
>
> The example below disproves it: `INT_MAX + 1 -1` is `INT_MAX - 1` with
saturation and `INT_MAX` with wrapping behaviour.


> > In particular, for example, in an expression of the form `a + b - c`, if
> `a + b` overflows but the final result fits in the type, you will actually
> get the correct result if the type is specified to wrap.
>
> That is not true in the general case, here you assumed that "a>0, b>0,
> c>=0". What about the case when "c<=0" or "a<0, b<0, c<=0"? Also, if the
> compiler can deduce that a, b, c are non negative and that "a+b" does not
> overflow because it is UB, then it can transform "(a+b)-c" into "a+(b-c)"
> thereby giving a permissible defined behavior; note that this
> transformation would also be permissible if the overflow behavior is
> defined to wrap but this wouldn't optimize anything, although if this would
> change behavior if both trapping and wrapping were required (gcc doesn't
> allow both, but in principle it could).
>
For any a, b, c, `a + b - c` (when evaluated with infinite range) before
taking modulo 2^w is the same as taking modulo 2^w after every step. Thus,
if taking modulo 2^w results in the same value, it is necessarily the case
that the value is equal to evaluating it while wrapping intermediate steps.
This is true even if the intermediate steps are reassociated, because
addition and subtraction are mutually associative (subtraction is just
addition by the additive inverse, also a property that only holds under
wrapping arithmetic, incidentally).
The proof, relies on the property that a ≡ b (mod N) <=> a + c ≡ b + c (mod
N).

Regarding transformations, Erroneous behaviour means the implementation
can decide whether or not to wrap or trap. gcc can do the transformation
when opts are on, and not do the transformation when sanitizers are on.
Incidentally, it is not correct for saturating arithmetic, because
saturating addition is *not* associative in signed integers, and the proof
is unsound when overflow is UB. Compilers absolutely can rely on the
property (since they can just define wrapping behaviour), but users cannot.

In general, for a signed integer expression "(a ± b) ± c" if "a ± b" did
> overflow then the result of the hole expression may be off by 2^N or
> 2^(N+1) if a second overflow occurs. If you keep on chaining additions or
> subtractions then the distance from the true result will be in the set {0,
> 2^N, 2^(N+1), 2^(N+2), ... 2^(N+M)} adding one possible erroneous value for
> each operation performed, giving you a low probably of yielding the
> mathematical result. I'm not even gonna try to count the number erroneous
> possibilities if you put in the multiplication and division operations, but
> it is clear that the probability space explodes in numbers.
>
>
> On September 4, 2025 9:46:56 a.m. EDT, Brian Bi via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>>
>>
>> On Thu, Sep 4, 2025 at 7:35 AM David Brown via Std-Proposals <
>> std-proposals_at_[hidden]> wrote:
>>
>>> On 04/09/2025 07:48, Peter C++ via Std-Proposals wrote:
>>> > FWIW
>>> >
>>> > one can implement efficient non-UB integer replacement types, eg, for
>>> > safety critical systems using enum class types as representation.
>>> >
>>> > see for example: https://github.com/PeterSommerlad/PSsimplesafeint
>>> > <https://github.com/PeterSommerlad/PSsimplesafeint>
>>> >
>>> > Regards
>>> > Peter
>>> >
>>>
>>> You can indeed make such classes - and give them dangerously misleading
>>> names like "Simple Safe Int".
>>>
>>> It is fine to implement wrapping signed integer classes if that's what
>>> you want - it is /not/ fine to pretend they are somehow safe or suitable
>>> for safety-critical systems. It doesn't matter if your overflows wrap -
>>> if you have an overflow, your safety-critical system is screwed.
>>>
>>> (Some other aspects of your library /are/ positive for safety, such as
>>> disallowing mixed signed operations and limiting certain other
>>> operations. It's only the overflow behaviour I am in strong
>>> disagreement about.)
>>>
>>>
>>> You'd be a lot better off with :
>>>
>>> using SignificantlySaferIntType = int64_t;
>>>
>>> That would eliminate the vast majority of signed integer overflow bugs,
>>> and give the /correct/ answer to 32-bit overflows.
>>>
>>> And anyone writing new code with enough care about overflow and code
>>> correctness that they might consider something beyond just using "int",
>>> will already be able to handle overflow in various ways - such as
>>> sanitizing values before calculations ("look before you leap", rather
>>> than trying to find a way to recover from jumping off a cliff and
>>> somehow ending up miles in the air). The checked arithmetic functions
>>> in C++26 are another option.
>>>
>>>
>>>
>>>
>>> I am in favour of integer class libraries that handle overflow in
>>> different ways. But the naming should be based on what is actually done
>>> - undefined behaviour, wrapping, zeroing, throwing, trapping, calling
>>> terminate/abort/whatever, saturating, returning an unspecified but valid
>>> value, setting errno, etc.
>>>
>>>
>>> I am not in favour of pretending or implying that the use of a
>>> particular integer class makes the code "safe" (or even just "safer").
>>>
>>
>> I think that actually, wrapping behaviour is safe*r* than UB, saturating
>> behaviour, or producing an unspecified value. I won't claim that it's
>> completely safe.
>>
>> Why? Because it's the choice that doesn't result in information loss when
>> overflow occurs. In particular, for example, in an expression of the form
>> `a + b - c`, if `a + b` overflows but the final result fits in the type,
>> you will actually get the correct result if the type is specified to wrap.
>>
>>
>>>
>>> --
>>> Std-Proposals mailing list
>>> Std-Proposals_at_[hidden]
>>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>>
>>
>> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>

Received on 2025-09-04 17:20:27