C++中的位运算符是否适用于__int128?

5
我正在进行一个项目,需要将__int128转换为uint8_t向量。
我已经有一个将长整型转换为uint8_t向量的函数。
我想做的是:
__int128 data = 0x5bc5ddd975d34ed0b4f18b410e7d2480
addLong(data >> 64);
addLong(data & 0xFFFFFFFFFFFFFFFF);

我得到了这个结果:00000000b4f18b41e7d2480。
你可以看到,第二部分的64个较少有效字节被正确处理,但是最高位并没有被正确处理。你知道这是否可能吗?

做这样的事情:

std::cout << std::hex << (long)(data >> 48) << std::endl;

这给了我结果:b4f1。
这就是为什么我认为位运算符在使用 __int128 时不起作用。

2个回答

7

遗憾的是,gcc 仍然不支持 __int128 / unsigned __int128 的字面量(就像 long long 没有后缀一样)。

你的示例应该会产生一个警告,指出你的字面值被截断为 long long 值:
godbolt

warning: integer constant is too large for its type
    |     __int128 data = 0x5bc5ddd975d34ed0b4f18b410e7d2480;
    |

(如果您没有得到一个警告,那么您很可能已经禁用了-Wconversion - 它默认是启用的)

按位运算应该没有问题。

如果您想要利用标准库函数(std::abs等)来处理128位整数,您需要确保您不是在严格的标准模式下编译(即-std=c++20),而是在标准的gnu方言下编译(即-std=gnu++20)(如果您根本没有指定-std,则这是默认设置)- 否则,std::abs等的int128重载将不可用


用户自定义字面量

利用 C++20 的 consteval用户自定义字面量,可以为 __int128 构建自定义编译时字面量。
(在 C++20 之前,可以通过将 consteval 替换为 constexpr 来实现,但在这种情况下,如果字面量存在问题,您将无法获得编译时错误)

虽然解析 int 需要很多样板代码,但如果您的程序中有大量的 __int128 字面量,则可能值得这样做:
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;

这样您就可以像下面这样编写__int128 / unsigned __int128 文字:
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;

5
位运算符可以使用,但是你的初始化有问题。整数字面量不能超过long long
我建议添加一个初始化帮助函数(如果在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';
}

输出

5bc5ddd975d34ed0
b4f18b410e7d2480

这适用于有符号数吗? - Mark Ransom
@MarkRansom 这有点棘手,但只要 high 的上移不会溢出,那么一切都应该没问题。虽然我不太习惯使用这些扩展类型。也许我应该将 highlow 都设为无符号类型,然后让操作员在之后转换为有符号类型? - Ted Lyngmo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接