Home > other >  Are bitwise operator working on __int128 in c
Are bitwise operator working on __int128 in c

Time:01-10

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
  • Related