【问题标题】:Unsigned 128-bit division on 64-bit machine64 位机器上的无符号 128 位除法
【发布时间】:2010-12-24 14:50:14
【问题描述】:

我有一个 128 位数字存储为 2 个 64 位数字(“Hi”和“Lo”)。我只需要将它除以一个 32 位数字。使用来自 CPU 的本机 64 位操作,我该怎么做?

(请注意,我不需要任意精度库。只需要知道如何使用本机操作进行这种简单的除法即可。谢谢)。

【问题讨论】:

    标签: 64-bit division integer-division 128-bit


    【解决方案1】:

    如果您使用架构可以处理的最大可能本地表示(64 位)存储值(128 位),您将在处理除法的中间结果时遇到问题(正如您已经发现的 :))。

    但您始终可以使用较小的表示形式。那么四个 32 位的数字呢?这样您就可以使用本机 64 位操作而不会出现溢出问题。

    可以找到一个简单的实现(在 Delphi 中)here

    【讨论】:

    • 首先,外部代码链接无效。而且您不需要在 64 位机器上将其作为四个 32 位数字处理。如果他们有本机除法指令,他们将有办法将 128 位数字除以 64 位数字。您的 delphi 代码也不会像 OP 想要的那样编​​译为本地除法指令
    【解决方案2】:

    如何使用来自 CPU 的本机 64 位操作来做到这一点?

    由于您想要 native 操作,因此您必须使用一些内置类型或内部函数。 所有以上答案只会为您提供一般的 C 解决方案,不会编译为除法指令

    大多数现代 64 位编译器都有一些方法可以进行 128×64 除法。在 MSVC 中使用 _div128()_udiv128() 所以你只需要调用 _udiv128(hi, lo, divisor, &remainder)

    _div128 内在函数将 128 位整数除以 64 位整数。返回值保存商,内在函数通过指针参数返回余数。 _div128 是 Microsoft 特定的。

    在 Clang、GCC 和 ICC 中有一个 __int128 类型,您可以直接使用它

    unsigned __int128 div128by32(unsigned __int128 x, uint64_t y)
    {
        return x/y;
    }
    

    【讨论】:

    • 注意_udiv128如果结果不适合64位会导致整数溢出错误。
    【解决方案3】:

    我有一个DECIMAL 结构,它由三个 32 位值组成:Lo32、Mid32 和 Hi32 = 总共 96 位。

    您可以轻松地将我的 C 代码扩展为 128 位、256 位、512 位甚至 1024 位除法。

    // in-place divide Dividend / Divisor including previous rest and returning new rest
    static void Divide32(DWORD* pu32_Dividend, DWORD u32_Divisor, DWORD* pu32_Rest)
    {
        ULONGLONG u64_Dividend = *pu32_Rest;
        u64_Dividend <<= 32;
        u64_Dividend |= *pu32_Dividend;
    
        *pu32_Dividend = (DWORD)(u64_Dividend / u32_Divisor);
        *pu32_Rest     = (DWORD)(u64_Dividend % u32_Divisor);
    }
    
    // in-place divide 96 bit DECIMAL structure
    static bool DivideByDword(DECIMAL* pk_Decimal, DWORD u32_Divisor)
    {
        if (u32_Divisor == 0)
            return false;
    
        if (u32_Divisor > 1)
        {
            DWORD u32_Rest = 0;
            Divide32(&pk_Decimal->Hi32,  u32_Divisor, &u32_Rest); // Hi FIRST!
            Divide32(&pk_Decimal->Mid32, u32_Divisor, &u32_Rest);
            Divide32(&pk_Decimal->Lo32,  u32_Divisor, &u32_Rest);
        }
        return true;
    }
    

    【讨论】:

    • 这不会像 OP 想要的那样编​​译为本机 CPU 划分指令
    • 他没有谈论任何特定的编译器。使用 ULONGLONG == unsigned __int64 的除法和模数直接在 64 位 CPU 上通过一条指令计算。研究汇编程序的输出,你会看到。
    • 是的,您的代码将编译为一长串指令,而不是相应的 64 位架构中的单个 div 指令。您的代码也远非可移植,因为标准中没有 DWORD 或 ULONGLONG
    • 您自己看看差异godbolt.org/z/k2cHmC 当然,64 位除法和取模是在一条指令中完成的,但是您正在执行这些除法中的很多,而不仅仅是一个(因为您已经可以在 64 位架构中直接将 128 位数字除以 64 位数字)
    • 好的。你说的对。顺便说一句:C 或 C++ 永远不可移植。我试图将 Linux 项目移植到 Visual Studio。这总是一场噩梦。我的代码被认为不会被复制和粘贴。我只是展示了一种简单的方法来划分任意大小的数字。甚至比 128 位大得多。可能我的回答对通过谷歌搜索“128 位除法”来这里的其他人有用,并且不在乎代码是否是内在的。顺便说一句:您的 _div128() 解决方案需要 VS 2019。我的代码在任何地方都有效。
    猜你喜欢
    • 2018-07-18
    • 2015-05-02
    • 2019-11-01
    • 2022-01-13
    • 2019-11-01
    • 2012-02-23
    • 2018-11-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多