我需要在C / C ++中计算这个等式:
x=(a*b-1)/c;
具有__int64类型的a,b,c,x(a,b,c,x <10 ^ 13)。在所有情况下,选择a,b,c使x适合__int64 但是,a * b非常大会导致溢出而x是错误的 我尝试通过类型转换来分离a * b:
x=(__int64)(((double)a/c)*(double)b - 1.0/c);
这样,首先计算a / c,不发生溢出错误 然而,问题是((double)a / c)*(double)b有时具有很大的价值(大约数十亿)并且精度降低,所以1.0 / c(非常小)不会产生任何影响并导致内部错误+ -1。
例如:(__ int64)(((double)a / c)*(double)b = 123456789.01更可能变为123456789.0和1.0 / c = 0.02。在这种情况下,存在+1的错误。
有没有办法在没有外部库(如Boost或Bignum)的情况下精确计算x?即使有错误+ -1也会搞砸我的代码
提前谢谢。
顺便说一句,我使用Visual Studio 10。
答案 0 :(得分:3)
您可以手动实现长算术,使用32位块:
长乘法:
(ax + b) * (cx + d)
= ac x^2 + (ad+bc) x + bd
[ab]*[cd]=[efg]:
//long long e,f,g
g = (long long)b*d;
f = (long long)a*d+b*c;
e = (long long)a*c;
//perform carry
f += g>>32; g &= 0xFFFFFFFF
e += f>>32; f &= 0xFFFFFFFF
学校分工,假设无符号算术:
[efg]/[hi]=[jkl]:
[jk] = [ef]/[hi];
r = [ef]-j*[hi];
l = [rg]/[hi];
if j > 0, result doesn't fit
x = [kl];
如果a
和b
已签名,请先修复该符号并使用绝对值进行计算,如@Serge所示:
如果a或b为零,x=(-1)/c
否则,sign(x)=sign(a)*sign(b)*sign(c)
答案 1 :(得分:2)
如果您的代码可以依赖于CPU,最简单的方法可能是使用汇编程序来保留高8字节。 x64,假设结果适合8个字节:
__asm{
MOV RAX, a
MUL b
SUB RAX, 1
SBB RDX, 0
DIV c
MOV x, RAX
}
答案 2 :(得分:0)
尝试找出a的最大乘数,它不会溢出。例如,如果* 4会溢出,那么部分地执行:
(a*5) = (a*3) + (a*2)
所以,如果你找到像
这样的中等价值b1, b2, b3, where b1+b2+b3 == b
然后
x = a*b1/c + a*b2/c + a*b3/c - 1.0/c
你会发现b1 = floor(MAX_INT / a)的最大可用值,那么只有当b-b1
答案 3 :(得分:0)
您可以重新安排计算:
x = (a*b - 1) / c
= a*b/c - 1 / c
= (a/c)*b - 1 / c ;If a is likely to be huge
= a*(b/c) - 1 / c ;If b is likely to be huge
问题在于精度的损失。这可以通过跟踪值的范围和引入比例因子(选择使中间值尽可能大而不溢出)来最小化。
例如,如果a
可能是0到1024,b
可能是0到16777215,c
可能是4096到8192;然后:
x = (a * ((256*b) /c) - 256 / c) / 256
在这种情况下(256*b) <= 0xFFFFFF00
(尽可能大以避免溢出32位无符号整数),( (256*b) / c) <= 0x000FFFFF
和a * ((256*b) /c) <= 0x3FFFFC00
。
同样对于这种情况,a*b
(来自原始公式)会溢出32位无符号整数;并且b/c
(来自第一次重新排列)将比(256 * b) / c
丢失8位精度。
当然,对于您的特定情况,最佳公式(在没有溢出的情况下提供最低精度损失的公式)取决于特定情况下变量的可能范围。