除以整数

时间:2016-05-16 14:46:40

标签: floating-point division numerical-methods rounding-error

我试图通过将一个整数乘积除以另一个整数乘积的比率来形成双精度浮点数(64位)。我希望以减少舍入误差的方式这样做。

我熟悉Kahan加法和减法求和。什么技术适用于分裂?

分子是许多长值(数万)的乘积,同样是分母。我也希望防止溢出和下溢。 (一个应用程序通过在足够数量的术语之后停止来估计无限产品。)

我尝试过的一件事是将易于分解的数字(使用已知质数的试验除法高达一百万)和取消共同因素进行分析,这有助于,但还不够。我的错误大约是1.0E-13。

我正在使用C#,但欢迎任何使用IEEE标准浮点数的代码。

研究:

我遇到了一篇很好的论文,讨论了+ - x /,Horner规则(多项式)和平方根的EFT(无误差变换)。标题是Philippe Langlois的“浮点4算术中的4个准确4算法”。见http://www.mathematik.hu-berlin.de/~gaggle/S09/AUTODIFF/projects/papers/langlois_4ccurate_4lgorithms_in_floating_point_4rithmetic.pdf

上面我指的是卡普和马克斯坦(分裂):https://cr.yp.to/bib/1997/karp.pdf

4 个答案:

答案 0 :(得分:3)

什么技术适合分裂?

对于除法a/b,您可以评估残差(余数):

a = b*q + r

如果您使用fusion-multiply-add

,则可以轻松访问此余数r
q = a/b ;
r = fma(b,q,-a) ;

同样的fma技巧可以应用于乘法:

y = a*b ;
r = fma(a,b,-y) ; // the result is y+r

然后,如果您在产品(a0+ra) / (b0+rb)之后最终得到两个近似操作数,则您对(a0+ra) = q*(b0+rb) + r感兴趣。
您可以先评估:

q0 = a0/b0 ;
r0 = fma(b0,q0,-a0);

然后将余数近似为:

r = fma(q0,rb,r0-ra);

然后将商更正为:

q = q0 + r/b0;

编辑:如果fma不可用怎么办?

我们可以使用精确的产品àdekker来模拟fma,它被分解为2个浮点的精确总和,然后是Boldo-Melquiond roundToOdd技巧,以确保3个浮点的总和完全舍入。 / p>

但这会有点矫枉过正。我们仅使用fma来评估残差,因此我们通常非常接近-ab。在这种情况下,ab + c是精确的,我们只有2个浮点来求和,而不是3。

无论如何,我们只粗略估计一堆操作的剩余误差,所以这个残差的最后一点不会那么重要。

所以fma可以这样写:

/* extract the high 26 bits of significand */
double upperHalf( double x ) {
    double secator = 134217729.0; /* 1<<27+1 */
    double p = x * secator; /* simplified... normally we should check if overflow and scale down */
    return p + (x - p);
}

/* emulate a fused multiply add: roundToNearestFloat(a*b+c)
   Beware: use only when -c is an approximation of a*b
   otherwise there is NO guaranty of correct rounding */
double emulated_fma(a,b,c) {
    double aup = upperHalf(a);
    double alo = a-aup;
    double bup = upperHalf(b);
    double blo = b-bup;

    /* compute exact product of a and b
       which is the exact sum of ab and a residual error resab */
    double high = aup*bup; 
    double mid  = aup*blo + alo*bup;
    double low  = alo*blo;
    double ab = high + mid;
    double resab = (high - ab) + mid + low;

    double fma = ab + c; /* expected to be exact, so don't bother with residual error */
    return resab + fma;
}

嗯,比一般的模拟fma稍微有点过分,但是使用一种为这部分工作提供原生fma的语言可能会更聪明......

答案 1 :(得分:2)

您正在寻找的Kahan求和的乘法等价是“双倍乘法”。在这里,如果您的整数可以表示为double值,则来自crlibm的函数Mul122就足够了。

#define Mul122(resh,resl,a,bh,bl)                 \
{                                                 \
    double _t1, _t2, _t3, _t4;                    \
                                                  \
    Mul12(&_t1,&_t2,(a),(bh));                    \
    _t3 = (a) * (bl);                             \
    _t4 = _t2 + _t3;                              \
    Add12((*(resh)),(*(resl)),_t1,_t4);           \
}

bhbl是以额外精度存储的正在运行的产品,作为两个double值的总和。 a是下一个整数(我们假设它完全转换为double)。 reshresl会收到下一个正在运行的产品,其中已考虑因素a

为了避免下溢和溢出,可以将指数外部化为所需宽度的整数。这是通过定期将frexp函数应用于正在运行的产品的高部分来完成的,然后通过将两个组件除以相同的2的幂来对运行的产品进行标准化(跟踪运行产品的两个总功率)已划分可以在一边用所需宽度的整数变量完成。

应用frexp的频率取决于您对正在乘以的整数的界限。如果整数低于2 53 ,这有助于将它们精确地表示为double值,那么在必须对运行产品进行标准化之前,您可以执行大约19次乘法,因为双精度指数上升到1023。

计算出与分子和分母对应的乘积后,丢弃低分量,然后除去高分量。这只会引入大约1ULP的错误。您的目标不是误差小于双精度ULP,是吗?

不要忘记你为分子和分母留下的两个人的力量!减去它们并使用ldexp函数将差值应用于商。

答案 2 :(得分:2)

除法不会受到与加法和减法相同的灾难性消除效应,并且使用IEEE浮点数被正确舍入,因此应该具有约1/2 ulps(~2e-16)的相对误差。任何大于此的错误很可能是中间产品的结果,因此需要注意这些错误。

Dekker (1971)有一些扩展基本数学运算精度的算法:正如另一个答案所指出的,如果你有权访问fma操作,可以简化这些算法。

答案 3 :(得分:0)

如果您可以访问FMA(融合乘法 - 添加),则其他答案很好,但C#不会使用它。我继续寻找快速解决方案,但我找到了一个准确的解决方案。

步骤1:分别收集分子和分母。

步骤2:剥离标志并计算有多少乘数为负数以了解答案的符号。

步骤3:遍历所有数字,计算每个数字的自然日志。

步骤4:累计分子和分母日志的单独补偿金额。 (使用Kahan求和。)

步骤5:取两个和之间的差值并计算指数。

第6步:恢复标志。

我对分子中的100,000个随机整数和分母中的相同数字进行了测试,但两个集合以不同的随机顺序进行了混洗。如果我使用常规乘法和除法的朴素方法,我的累积误差约为2x10 ^ -15。使用我的补偿日志方法,错误为零。 (我很幸运?)我会对更难的案例进行更多测试。然而,通过补偿日志的总和,我得到的精度几乎是最终舍入之前的两倍。

我很惊讶它运作良好。显然,执行200,000个对数并不理想。

理论说明:

累积舍入误差就像随机游走一样。在N次计算之后,您可能会遇到sqrt(N)* ULP / 2的错误。如果ULP / 2是5.0E-18且N是200,000,那么你得到的是2.2E-15,这与我的天真方法很接近。