7

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.

bolov
  • 72,283
  • 15
  • 145
  • 224
  • Is `(__int128)1<<127` considered a UDL? What about `~MAX_INT128`, where `MAX_INT128` is the UDL that is working for you? – barak manos Aug 05 '14 at 18:38
  • How is your UDL defined? AFAIK, you cannot have a parameter type that is larger than `unsigned long long`. – Praetorian Aug 05 '14 at 18:40
  • 1
    @Praetorian you are thinking about cooked literals. Raw literals give every digit individually in a C-string, so you are not bound by types (like `long long`) – bolov Aug 05 '14 at 18:49
  • @barakmanos (__int128)1<<127 is not an UDL – bolov Aug 05 '14 at 18:58
  • See https://groups.google.com/a/isocpp.org/forum/?fromgroups#!topic/std-discussion/Chud-sdo7uA – dyp Aug 05 '14 at 19:10

1 Answers1

6

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.

  • so you are saying that if I write the constant `-2147483648` (in a system where int is 32-bits) that’s UB? – bolov Aug 05 '14 at 19:04
  • 1
    @bolov No, but `2147483648` will have type `long long`, so the expression does not have the intended meaning. FWIW, the `long long` version (typically `-9223372036854775808`) is invalid. Not undefined behaviour, but if there is no signed integer type that can hold `9223372036854775808`, then using `9223372036854775808` is always an error. –  Aug 05 '14 at 19:08
  • 2
    @bolov: `-2147483648` is not a literal. It's an expression consisting of a literal `2147483648` with a unary `-` operator applied to it. The unary `-` can overflow. – Keith Thompson Aug 05 '14 at 19:41
  • Right. INT_MAX is 2147483647. – emsr Aug 06 '14 at 13:27