I'm working on a project where I have to transform a __int128 into a vector of uint8_t.
I have already a function that transform a long into a vector of uint8_t.
I wanted to do something like :
__int128 data = 0x5bc5ddd975d34ed0b4f18b410e7d2480
addLong(data >> 64);
addLong(data & 0xFFFFFFFFFFFFFFFF);
and I'm getting this as a result : 00000000b4f18b41e7d2480
As you can see, the second part, the 64 less significant bytes are correctly treated, but the most ones aren't.
Do you know if this is possible ?
Doing something like that :
std::cout << std::hex << (long)(data >> 48) << std::endl;
gives me the result : b4f1.
That's why I think bitwise operator aren't working with __int128.
CodePudding user response:
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;
CodePudding user response:
The bitwise operators work, but your initialization does not. The integer literal can't be bigger than a long long
.
I suggest adding a helper function for the initialization (if there isn't already one in gcc):
#include <cstdint>
#include <iostream>
__int128 init_int128(std::int64_t high, std::uint64_t low) {
return __int128(high) << 64 | low;
}
int main() {
__int128 data = init_int128(0x5bc5ddd975d34ed0, 0xb4f18b410e7d2480);
std::cout << std::hex;
std::cout << static_cast<std::int64_t>(data >> 64) << '\n';
std::cout << static_cast<std::uint64_t>(data) << '\n';
}
Output
5bc5ddd975d34ed0
b4f18b410e7d2480