gcc unfortunately still doesn't support __int128
/ unsigned __int128
literals (there's no suffix for them like ll
for long long).
Your example should produce a warning that your literal value gets truncated to a long long
value:
godbolt
warning: integer constant is too large for its type
| __int128 data = 0x5bc5ddd975d34ed0b4f18b410e7d2480;
|
(if you don't get one then you've most likely disabled -Wconversion
- it's enabled by default)
The bitwise operations should work without problems.
In case you want to utilize the standard library functions (std::abs
, etc...) for 128-bit ints you need to make sure that you're not compiling in a strict standard mode (i.e. -std=c++20
), but rather in a gnu dialect of the standard (i.e. -std=gnu++20
) (this is the default if you don't specify -std
at all) - otherwise the int128 overloads for std::abs
, etc... won't be available.
User-defined literals
Utilizing C++20 consteval
and user-defined literals its possible to build a custom compile-time literal for __int128
.
(it would be possible to use this pre-C++20 by substituting consteval
with constexpr
, but in that case you wouldn't get compile-time errors if something is wrong with the literal)
It is a lot of boilerplate for int parsing though, but it might be worth it in case you have a lot of __int128
literals within your program:
godbolt
#include <stdexcept>
#include <cctype>
#include <bit>
namespace int128_literal {
// determines the base of the given digit string
// and increments str past it
consteval int determine_base(const char*& str) {
int base = 10;
if(str[0] == '0') {
if(str[1] == 'x' || str[1] == 'X') {
// hexadecimal
str += 2;
base = 16;
} else if(str[1] == 'b' || str[1] == 'B') {
// binary
str += 2;
base = 2;
} else if(
str[1] == '0' || str[1] == '1' || str[1] == '2' ||
str[1] == '3' || str[1] == '4' || str[1] == '5' ||
str[1] == '6' || str[1] == '7'
) {
// octal
str += 1;
base = 8;
} else if(str[1] == '\0') {
// zero literal
base = 8;
} else {
throw std::logic_error("unknown literal prefix!");
}
}
return base;
}
// parses the given hexadecimal digit.
// returns -1 for the digit separator (')
consteval int parse_digit(char character) {
switch(character) {
case '\'':
// digit separator
return -1;
case '0':
return 0;
case '1':
return 1;
case '2':
return 2;
case '3':
return 3;
case '4':
return 4;
case '5':
return 5;
case '6':
return 6;
case '7':
return 7;
case '8':
return 8;
case '9':
return 9;
case 'a':
case 'A':
return 10;
case 'b':
case 'B':
return 11;
case 'c':
case 'C':
return 12;
case 'd':
case 'D':
return 13;
case 'e':
case 'E':
return 14;
case 'f':
case 'F':
return 15;
default:
throw std::logic_error("Unknown digit in literal!");
}
}
consteval unsigned __int128 parse(const char* str) {
// nullptr
if(!str)
throw std::logic_error("nullptr!");
bool is_negative = false;
if(*str == '-') {
str++;
is_negative = true;
}
// determine base
int base = determine_base(str);
int parsed_digits = 0;
unsigned __int128 value = 0;
while(*str != '\0') {
int digit = parse_digit(*str);
// digit separator
if(digit == -1) {
if(parsed_digits == 0)
throw std::logic_error("digit separator not allowed at beginning of literal!");
else if(*(str + 1) == '\0')
throw std::logic_error("digit separator not allowed at end of literal!");
str++;
continue;
}
// check if digit is allowed in current base
switch(base) {
case 2:
if(digit > 1)
throw std::logic_error("only 0-1 allowed for binary!");
break;
case 8:
if(digit > 7)
throw std::logic_error("only 0-7 allowed for octal!");
break;
case 10:
if(digit > 9)
throw std::logic_error("only 0-9 allowed for decimal!");
break;
}
unsigned __int128 next_value = value * base;
// detect overflow during multiply
if(next_value / base != value)
throw std::logic_error("literal too large for unsigned __int128!");
next_value += digit;
// detect overflow during addition
if(next_value < value) {
throw std::logic_error("literal too large for unsigned __int128!");
}
value = next_value;
str++;
parsed_digits++;
}
if(parsed_digits == 0) {
throw std::logic_error("no digits in literal!");
}
// negate two's complement
if(is_negative) {
value = ~value + 1;
}
return value;
}
consteval unsigned __int128 operator""_uint128(const char* str)
{
return parse(str);
}
consteval unsigned __int128 operator""_uint128(const char* str, std::size_t)
{
return operator""_uint128(str);
}
consteval __int128 operator""_int128(const char* str)
{
unsigned __int128 value = parse(str);
return std::bit_cast<__int128>(value);
}
consteval __int128 operator""_int128(const char* str, std::size_t)
{
return operator""_int128(str);
}
}
using int128_literal::operator""_int128;
using int128_literal::operator""_uint128;
This would then allow you to write __int128
/ unsigned __int128
literals like this:
godbolt
// numeric literal:
__int128 a = 0x5bc5ddd975d34ed0b4f18b410e7d2480_int128;
unsigned __int128 b = 340'282'366'920'938'463'463'374'607'431'768'211'455_uint128;
__int128 c = -0b11111111111111010101010101010101001010101010010101001_int128;
__int128 d = 075642412376_int128;
// or as string literal:
__int128 a = "0x5bc5ddd975d34ed0b4f18b410e7d2480"_int128;
unsigned __int128 b = "340'282'366'920'938'463'463'374'607'431'768'211'455"_uint128;