方程中的多重/分裂困境

时间:2012-10-20 02:41:06

标签: c++ c

我需要在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。

4 个答案:

答案 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];

如果ab已签名,请先修复该符号并使用绝对值进行计算,如@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
}

[1] http://en.wikibooks.org/wiki/X86_Assembly/Arithmetic

答案 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) <= 0x000FFFFFa * ((256*b) /c) <= 0x3FFFFC00

同样对于这种情况,a*b(来自原始公式)会溢出32位无符号整数;并且b/c(来自第一次重新排列)将比(256 * b) / c丢失8位精度。

当然,对于您的特定情况,最佳公式(在没有溢出的情况下提供最低精度损失的公式)取决于特定情况下变量的可能范围。