【问题标题】:Avoiding overflow in integer multiplication followed by division避免整数乘法和除法溢出
【发布时间】:2011-04-04 17:22:52
【问题描述】:

我有两个整数变量 ab 和一个常量 s 分别。 d。我需要分别计算(a*b)>>s 的值。 a*b/d。问题是乘法可能会溢出,即使a*b/d 可以适合给定的整数类型,最终结果也不会正确。

如何有效地解决这个问题?直接的解决方案是将变量ab 扩展为更大的整数类型,但可能没有更大的整数类型。有没有更好的方法来解决这个问题?

【问题讨论】:

  • 我推断d 总是1<<s。这是真的吗?
  • 最好的办法是扩展成更大的整数类型。
  • 总有一个更大的整数类型:struct uint_twice_max { uintmax_t upper, lower; };。我将把操作定义留给你:p
  • @pwny:这可能会导致准确性降低,尤其是当db 很大时。
  • 你想要一个四舍五入的积分还是一个浮点数作为答案?

标签: c++ c algorithm numbers


【解决方案1】:

如果没有更大的类型,你要么需要找到一个 big-int 样式库,要么手动处理它,使用长乘法。

例如,假设 ab 是 16 位的。然后您可以将它们重写为a = (1<<8)*aH + aLb = (1<<8)*bH + bL(其中所有单独的组件都是8 位数字)。那么你知道总体结果将是:

(a*b) = (1<<16)*aH*bH
      + (1<<8)*aH*bL
      + (1<<8)*aL*bH
      + aL*bL

这 4 个组件中的每一个都适合 16 位寄存器。您现在可以执行例如在每个单独的组件上右移,小心处理适当的进位。

【讨论】:

  • 我认为上述问题是 ab 可能不适合 16 位,尽管 ab/d 保证适合。因此,您的解决方案没有帮助。
  • @Mark Ransom:确实如此。使用我的方法,您不需要 32 位值。
  • 是的,这可行,但在此解决方案中似乎存在一些浪费的计算。我想知道它是否不能以某种方式与除法(右移)合并以获得更有效的代码。
  • @Juraj:如果s 大于 8 或 16,那么是的,您可以消除一些不太重要的计算。但在一般情况下,计算所有这些可能会更快,而不是进行条件运算。
  • 如果 s 是编译时常量并且您使用模板 (C++),编译器将进行所有必要的优化。
【解决方案2】:

我没有对此进行详尽的测试,但是您是否可以先进行除法,然后再考虑余数,以牺牲额外的操作为代价?因为d是2的幂,所以所有的除法都可以简化为按位运算。

例如,总是假设a &gt; b(你想先除较大的数)。然后a * b / d = ((a / d) * b) + (((a % d) * b) / d)

【讨论】:

  • 我认为(a % d) * b 仍然可能溢出(最大可能值为(d-1)*b)。
  • 您可以使用与 Mark B 解释 ((a%d)*b)/d 部分相同的技术,将 a%d 视为 b 并将 b 视为 a。
  • @steabart:是的,这是真的。为了安全起见,您需要尽可能多地递归。
  • 分割成本很高。如此昂贵,以至于 VC++ 编译器通过溢出乘法和一些位摆弄来替换除法(具有常数分母)。
  • 如果 d &gt; a 则递归步骤是原始问题,这没有帮助
【解决方案3】:

如果较大的类型只有 64 位,那么直接的解决方案很可能会产生高效的代码。在 x86 CPU 上,两个 32 位数字的任何乘法都会在另一个寄存器中产生溢出。因此,如果您的编译器理解这一点,它就可以为Int64 result=(Int64)a*(Int64)b 生成高效的代码。

我在 C# 中遇到了同样的问题,编译器生成了非常好的代码。 C++ 编译器通常比 .net JIT 创建更好的代码。

我建议将代码转换为更大的类型,然后检查生成的汇编代码以检查它是否良好。

【讨论】:

  • 我查看了反汇编,int32_t result = (int64_t(a)*int64_t(b))&gt;&gt;2 生成了很好的代码(只是 imul 用于乘法)。但是当 b 是常量时,编译器会生成call _allmul。这似乎有点奇怪。我将不得不更深入地研究它。
【解决方案4】:

在某些情况下(历史上具有选定常数的 LCG 随机数生成器),对于 a 和 d 的某些值,可以做你想做的事情。

这称为施拉格方法,参见例如。 there.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-03-30
    • 1970-01-01
    • 2020-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-14
    相关资源
    最近更新 更多