Search code examples
c++c++11integer-overflowuser-defined-literalsint128

Integer overflow with UDL (user defined literal) for __int128 @ min negative value


For clarity and simplicity I will shorten the following numbers as follows:

  • −170,141,183,460,469,231,731,687,303,715,884,105,728 as -170…728
  • 170,141,183,460,469,231,731,687,303,715,884,105,727 as 170…727

These numbers represent the minimum and maximum values of an 128–bit signed integer (__int128 in gcc).

I implemented user–defined literals (raw literals) for this data type since gcc doesn’t offer a way of defining constants of this type: _u128 for unsigned __int128 and _i128 for __int128.

The minus character is not part of the UDL, but a unary minus operator applied to the result of the UDL.

So for a -ddddd_i128 (where d is a digit) the UDL computes a signed __int128 with the positive value ddddd and then the compiler will apply the unary minus operator to it. So far so good.

The problem is with -170…128_i128 (which should be a valid value for __int128):
the UDL computes the signed __int128 positive number 170…128 which is just outside of the range of __int128, resulting in Undefined Behavior (signed integer overflow).

Any solution to represent this number constant with a UDL?


My UDL’s are declared (just a non-constexpr, loopy version for now) (they are raw literals):

unsigned __int128 operator"" _u128(char const *str);
__int128 operator"" _i128(char const *str);

Some usages:

  1000000000000000000000000000000000_i128
  -1000000000000000000000000000000000_i128
  -170141183460469231731687303715884105728_i128 // <-- this has UB
  170141183460469231731687303715884105727_u128
  340282366920938463463374607431768211455_u128

I know that there are ways of defining the constant -170…728 with various ways, like bit shifts, mathematical operations, but I want to be able to create it in a consistent way, e.g. I don’t want this situation: you can create any constant using this UDL, except for -170…728_i128, for which you have to use extra tricks.


Solution

  • This is essentially the same problem that implementors have when implementing <limits.h>: INT_MIN cannot be defined (on a typical 32-bit system) as -2147483648. It can be (and commonly is) defined as (-2147483647 - 1) instead. You'll have to do something similar. There may not be any way to represent the most negative number with a single negation operator and literal, but that's okay: there is simply no need for it.