【问题标题】:Get the number of times uint64 has exceeded its limit while multiplying uint64 * uint64在乘以 uint64 * uint64 时获取 uint64 超过其限制的次数
【发布时间】:2020-07-07 15:33:25
【问题描述】:

我需要在 uint64_t 上乘以 uint64_t 时完成溢出量。这可以通过下面的公式计算,但我无法访问 128 位整数。

使用uint128_t的示例伪代码:

uint64_t x = ...
uint64_t y = ...
uint128_t result = (uint128_t)x * (uint128_t)y / ((uint128_t)1 << 64);

也可以通过将uint128_t 拆分为[uint64_t, uint64_t] 并执行示例中的计算来实现。我考虑不使用它,因为我担心它很慢。

【问题讨论】:

  • 您要计算result 还是“完成的溢出量”?
  • 什么是“溢出量”?实际结果(没有溢出/至少 128 位)和溢出结果之间的差异?但哪个溢出? C 没有指定溢出的效果
  • BTW (UINT64_MAX + 1) 可以为 0,因为 uint64_t 上的溢出,很可能 UINT64_MAX 被定义为 uint64_t,不好除以 0 ...
  • 我更新了代码,使其更像 C。我从未使用过uint128_t,但我认为这段代码现在大部分可以由拥有它的编译器运行。
  • 我看到 anatolyg 打败了我。

标签: c algorithm


【解决方案1】:

要制作一个“干净”(便携)的解决方案,请进行 32 位一半的乘法运算。

使用数学符号:

x = x1 * 2^32 + x0
y = y1 * 2^32 + y0

然后

x * y = x1 * y1 * 2^64 + (x1 * y0 + x0 * y1) * 2^32 + x0 * y0

这里,所有 xn * yn 都适合 64 位。唯一棘手的部分是在不丢失溢出(进位)位的情况下进行加法。

为此,您可以使用以下代码,该代码检查两个 64 位数字相加时是否发生溢出。

bool overflow(uint64_t x, uint64_t y)
{
    return x + y < x;
}

这是计算 128 位乘法的高 64 位部分的代码:

uint64_t doit(uint64_t x, uint64_t y)
{
    // Calculate 64-bit parts of the 128-bit result
    uint64_t x1 = x >> 32;
    uint64_t x0 = x << 32 >> 32;
    uint64_t y1 = y >> 32;
    uint64_t y0 = y << 32 >> 32;
    uint64_t part0 = x0 * y0;
    uint64_t part1 = x1 * y0;
    uint64_t part2 = x0 * y1;
    uint64_t part3 = x1 * y1;
    /// Use part3
    uint64_t result = part3;
    // Use the 32-bit high halves of part1 and part2
    result += part1 >> 32;
    result += part2 >> 32;
    // Throw away their high half; multiply by 2^32
    part1 <<= 32;
    part2 <<= 32;
    // Calculate the 65-bit sum of parts 1 and 2
    bool carry = overflow(part1, part2)
    result += carry;
    uint64_t temp = part1 + part2;
    // Use part0
    carry = overflow(temp, part0)
    result += carry;
    return result;
}

【讨论】:

  • 我正在编写与此类似的自己的函数,所以这里有一个与两者相似的链接,以及用于验证的__int128godbolt.org/z/j5H-qm 不幸的是,我认为你的某个地方有一个错误,因为它给出了不同的答案。
  • 已修复;代码现在可以工作了。对那个上帝螺栓链接的有趣观点!我想知道是否有可能说服编译器使用一条乘法指令生成代码。
  • 我的也有一些错误 ;-) 这是一个新链接,所有内容都已修复,我希望。 godbolt.org/z/SxvoaW
  • 看起来不错,但查找 overflow() 的步骤可以使用 32 位参数完成
  • 或许在result += part2 &gt;&gt; 32;之后,简化为return result + ((part2 &amp; UINT32_MAX) + (part1 &amp; UINT32_MAX) + (part0 &gt;&gt; 32)) &gt;&gt; 32;?
【解决方案2】:

英特尔 64 位架构有一条指令,可将两个 64 位数字相乘,得到 128 位结果。要访问它,你有_mulx_u64 intrinsic

乘法后,你只需将高 64 位部分作为你的result,并丢弃低部分。

【讨论】:

  • 这里看起来是最好的答案,但不幸的是我没有针对英特尔 CPU。
【解决方案3】:

首先让我说没有可移植的方式来做这件事,因为在 C++20 或更低标准中没有int128_t

考虑到这一点,在 Windows 中,您可以使用 MultiplyExtract128 函数来实现此目的。使用 64 作为移位参数除以2^64

【讨论】:

  • 当然可以移植——你用 64 位操作模拟 128 位乘法,例如通过小学长乘法。
  • 感谢您的回答,但问题是关于 C,而不是 C++。
  • 注意到了,对软件解决方案不感兴趣,但如果这就是您编写程序的方式,那么您将获得更多权力。
  • @AlexanderSadovskyi 那个函数是 C 函数,你是什么意思?
  • 如果 OP 无法访问任何特定于机器的内在函数或除便携式 C 之外的任何其他内容,似乎就是这种情况,那么我看不出还有什么可以做的。
猜你喜欢
  • 2014-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-26
  • 1970-01-01
  • 1970-01-01
  • 2010-12-13
  • 1970-01-01
相关资源
最近更新 更多