我试图在某些计算中避免long long
s和整数溢出,所以我想出了下面的函数来计算(a * b) / c
(由于截断整数除法,顺序很重要)。
unsigned muldiv(unsigned a, unsigned b, unsigned c)
{
return a * (b / c) + (a * (b % c)) / c;
}
是否存在任何不能按预期工作的边缘情况?
答案 0 :(得分:4)
EDITED:对于原始明显逻辑正确的值的超集,这是正确的。如果c
>它仍然没有给你买b
,可能还有其他条件。也许您对c
的价值观有所了解,但这可能没有您期望的那么多。 a
,b
,c
的某些组合仍会溢出。
编辑:假设您因严格的C ++ 98可移植性原因而避免使用long long
,那么通过推广您的unsigned
到double
,您可以获得大约52位的精确度有积分值来做数学。使用double
数学实际上可能比做三个整数除法更快。
答案 1 :(得分:4)
这在很多情况下都失败了。最明显的是当a
很大时,a * (b % c)
溢出。在这种情况下,您可以尝试更换a
和b
,但如果a
,b
和c
都很大,那么仍然会失败。考虑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)
答案 2 :(得分:0)
为了避免溢出,你必须预先划分然后乘以某个因子。
使用的最佳因素是c,只要a和b中的一个(或两个)大于c。这就是克里斯多德的功能。它具有((a%c)*(b%c))的最大中间值,正如Chris所指出的那样,小于或等于((c-1)*(c-1))。
如果你可能遇到a和b都小于c的情况,但是(a * b)仍然可能溢出(当c接近字大小的限制时可能就是这种情况)那么最好的因素是使用是两个的大功率,转向乘法并分为移位。尝试移动一半的字大小。
请注意,使用预分割然后后乘法相当于在没有更长的单词时使用更长的单词。假设你没有丢弃低阶位但只是将它们添加为另一个术语,那么你只是使用几个单词而不是一个更大的单词。
我会让你填写代码。