这个乘除函数是否正确

Is this multiply-divide function correct?

本文关键字:是否 函数      更新时间:2023-10-16

我试图避免在某些计算中long long s和整数溢出,所以我想出了下面的函数来计算(a * b) / c(由于截断整数除法,顺序很重要)。

 unsigned muldiv(unsigned a, unsigned b, unsigned c)
 {
      return a * (b / c) + (a * (b % c)) / c;
 }

是否有任何边缘情况无法按预期工作?

编辑:这对于原始明显逻辑正确的超集值是正确的。如果c> b,它可能在其他条件下,它仍然不会给你买任何东西。也许您对自己的c价值观有所了解,但这可能没有您期望的那么多帮助。abc的某些组合仍然会溢出。

编辑:假设您出于严格的C++98可移植性原因避免long long,您可以通过将unsigned提升到恰好具有整数值进行数学运算的double来获得大约52位的精度。 使用double数学实际上可能比做三个整数除法更快。

这在很多情况下都失败了。 最明显的是当a很大时,a * (b % c)溢出。 在这种情况下,您可以尝试交换ab,但如果abc都很大,这仍然会失败。 考虑 a = b = 2^25-1 和 c = 2^24,其中 32 位无符号。 正确的结果是 2^26-4,但 a * (b % c)b * (a % c) 都会溢出。 甚至(a % c) * (b % c)也会溢出。

到目前为止,解决这个问题

的最简单方法是有一个加宽的乘法,这样你就可以得到更高精度的中间产品。 如果你没有这个,你需要用较小的乘法和除法来合成它,这与实现你自己的 biginteger 库几乎是一回事。

如果您可以保证c足够小,以至于(c-1)*(c-1)不会溢出未签名的,则可以使用:

unsigned muldiv(unsigned a, unsigned b, unsigned c) {
    return (a/c)*(b/c)*c + (a%c)*(b/c) + (a/c)*(b%c) + (a%c)*(b%c)/c;
}

这实际上会给你所有a和b的"正确"答案 - (a * b)/c % (UINT_MAX+1)

为了避免溢出,你必须预先除法,然后乘以某个因素。

最好的使用因子是 c,只要 a 和 b 中的一个(或两个)大于 c。 这就是克里斯·多德的函数所做的。 它的最大中间体为((a % c) * (b % c)),正如Chris所指出的,它小于或等于((c-1)*(c-1))。

如果你遇到a和b都小于c的情况,但(a * b)仍然可能溢出(当c接近单词大小的限制时可能是这种情况),那么最好的使用因素是2的大幂,将乘法和除法转换为移位。 尝试将单词大小移半。

请注意,使用预除法然后后乘法等效于在没有更长单词可用时使用较长的单词。 假设您不丢弃低阶位,而只是将它们添加为另一个术语,那么您只是使用几个单词而不是一个较大的单词。

我会让你填写代码。

相关文章: