C++ Logo

std-proposals

Advanced search

Re: [std-proposals] D3666R0 Bit-precise integers

From: David Brown <david.brown_at_[hidden]>
Date: Tue, 2 Sep 2025 17:53:33 +0200
On 02/09/2025 16:47, connor horman via Std-Proposals wrote:
>
>
>
>
> On Tue, 2 Sept 2025 at 10:14, David Brown via Std-Proposals
> <std-proposals_at_[hidden] <mailto:std-proposals_at_[hidden]>>
> wrote:
>
> On 02/09/2025 15:38, Marcin Jaczewski wrote:
> > wt., 2 wrz 2025 o 14:49 David Brown via Std-Proposals
> > <std-proposals_at_[hidden]
> <mailto:std-proposals_at_[hidden]>> napisał(a):
> >>
> >> On 02/09/2025 14:24, Hans Åberg via Std-Proposals wrote:
> >>>
> >>>
> >>>> On 2 Sep 2025, at 14:14, Jan Schultke
> <janschultke_at_[hidden] <mailto:janschultke_at_[hidden]>> wrote:
> >>>>
> >>>> You seem to be confusing some mostly unrelated concepts.
> >>>>
> >>>>>> 1. C does not allow _BitInt(1); should C++ to make generic
> programming
> >>>>>> more comfortable?
> >>>>>
> >>>>> The ring ℤ/2ℤ of integers modulo 2, also a field, is
> isomorphic to the Boolean ring 𝔹 having exclusive or as addition and
> logical conjunction as multiplication.
> >>>>>
> >>>>> If bool 1+1 is defined to 0, then it is already in C++.
> >>>>
> >>>> Whether there is some other C++ thing that works
> mathematically the
> >>>> same doesn't say anything about whether _BitInt(1) is valid or
> should
> >>>> be valid. The issue is regarding a specific type.
> >>>
> >>> It could have been defined to be the same as bool.
> >>
> >> No, it could not. _BitInt(1), if it is to exist, has the two
> values -1
> >> and 0. Like all signed integer types, arithmetic overflow on it is
> >> undefined, and like all _BitInt types, there is no integer
> promotion.
> >> Thus for _BitInt(1), (-1) + (-1) is UB.
> >>
> >
> > Do we need UB here? This is not `int` and we could have all
> operations
> > defined. What would we gain from it?
>
> Do we /need/ UB on signed arithmetic overflow? No. Do we /want/ UB on
> signed arithmetic overflow? Yes, IMHO. I am of the opinion that it
> makes no sense to add two negative numbers and end up with a positive
> number. There are very, very few situations where wrapping
> behaviour on
> signed integer arithmetic is helpful - making it defined as wrapping is
> simply saying that the language will pick a nonsensical result that can
> lead to bugs and confusion, limit optimisations and debugging, and
> cannot possibly give you a mathematically correct answer, all in the
> name of claiming to avoid undefined behaviour.
>
> That is the mathematically correct answer for a modular arithmetic ring.

Sure. But people don't use "int" in programming to model modular rings
- they aim to get as close as reasonably possible to mathematical
integers, while remaining efficient in use.

If you want a modular ring, use unsigned types.

> In fact, as far as I can tell, any other result (other than an
> impossible one) is mathematically incorrect, because they are not
> invertible or associative (pick one or more, saturation is not
> invertible, trapping or other undefined behaviour shenanigans are none
> of the above).

Saturating arithmetic overflow would be the mathematically correct
result for saturating operations. Modulo (wrapping) overflow would be
the mathematically correct result for modulo operations.

When you want something roughly like mathematical integers, nothing can
give you a "correct" answer on overflow.

Trying asking a kid. Give them an empty egg box with space for 6 eggs.
Ask them to put in 4 eggs, then put in another 3 eggs. They will tell
you it can't be done - they won't tell you that you now have 1 egg (from
unsigned modulo 7) or -7 eggs (modelling a -7 to 6 wrapping signed
integer type).

When integer overflow is UB, you get nice mathematical properties. For
example, if a > 0 and b > 0, then a + b > a, a + b > b, and a + b > 0.
You don't get that with wrapping, saturation, or other defined behaviour
on overflow.

Oh, and overflow on UB is more "invertible" and associative than
wrapping, once you include multiplication and division. "x * y / y"
simplifies to "x" with UB overflow, but is totally different with
wrapping semantics.

A fixed size type cannot correctly and completely model mathematical
integers - it is clearly impossible. They can correctly and completely
model some things that programmers are not usually interested in - such
as different kinds of finite fields or rings. Or they can correctly
model mathematical integers as long as you are careful to limit the
values you use - and /that/ is what people actually want to do. UB on
overflow models that best.

> Whether or not it limits optimizations or debugging is
> another story, but it does produce the most mathematically correct
> answer.

Wrapping produces mathematically nonsense results on overflow - unless
you are specially looking for wrapping fields instead of approximating
mathematical integers. Since your program is highly unlikely to be
correct if expressions give nonsense results, you have gained nothing
over UB.

On the other hand, better debugging is /always/ useful in any
programming, and and if optimisations and efficient code are not
important to you, why are you using a language like C or C++ in the
first place? Stick to Python and use its unbounded integers, so you
always get mathematically correct results. (Or, I suppose, use a C++
unlimited precision integer library.)

> In fact, a good number of mathematical proofs do not apply to
> signed integers in C or C++, because they rely on the additive inverse.

And even fewer work correctly for wrapping integers.

> There are also optimizations that are not available to users (though
> compilers that define overflow as wrapping will have access to them).
> Namely, division through modular inverse requires all odd numbers to
> have a multiplicative inverse, which is only the case if the type forms
> a ring.

The great thing about UB is that it is /undefined/ - letting compilers
pick a definition if that's what suits them. So if you write "x / 3",
the compiler can generate a multiply and a shift by doing a
double-length multiplication. It can do that even if there is no native
type in the language with double the length of x. It can wrap as much
as it likes. When the language says overflow is UB, the compiler has
the freedom to assume it doesn't happen from user code - it also has the
freedom to implement any kind of behaviour it likes in order to give
more efficient results to the user as long as the correct results are
produced when no UB is executed.

>
> Remember, if you first /define/ a behaviour in the standards, you are
> stuck with it. Leave it as UB, and the compiler can do what it wants -
> perhaps helped by developer choices. Thus in gcc, you can choose to
> give signed integer arithmetic modulo behaviour by using the "-fwrapv"
> flag. You can choose to have the compiler generate run-time checks and
> stop the program with an error message on overflow using the
> "-fsanitize=signed-integer-overflow" flag. A compiler for a DSP could
> choose to use saturating arithmetic. A C interpreter could choose to
> use arbitrary precision arithmetic and give a run-time error for out of
> range values when assigning the result to a variable. A C++ compiler
> could have an option to throw an exception on overflow. And of
> course C
> and C++ compilers can optimise code on the assumption that overflow,
> being UB, does not occur, leading to smaller and faster code in some
> situations (typically in loops, and also noticeable in some kinds of
> array indexing on 64-bit machines).
>
> But if the standards say overflow is wrapping, you are stuck with it.
> Your code gives results that are almost certainly incorrect and
> unhelpful on overflow, but your tools can't help you find your mistake.
>
>
> This is why the C and C++ standards committees have both refused to
> define the behaviour of arithmetic overflow even when they have both
> restricted the latest language versions to requiring two's complement
> representation of signed integers.
>
> In C23, signed integer arithmetic overflow with _BitInt types is UB,
> just like for other signed integer types. That makes perfect sense to
> me. Hopefully the same will apply to C++23 _BitInt.
>
>
> (Sorry for the long and slightly ranting answer to a two line question!)
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden] <mailto:Std-Proposals_at_[hidden]>
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
> <https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals>
>
>

Received on 2025-09-02 15:53:39