【问题标题】:Minimize rounding error when dividing by integers除以整数时最小化舍入误差
【发布时间】:2016-05-16 14:46:40
【问题描述】:

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

我熟悉用于加减法的 Kahan 求和。哪些技术适用于除法?

分子是许多长值(数万)的乘积,分母也是如此。我也希望防止上溢和下溢。 (一个应用程序是通过在足够数量的项后停止来估计无限乘积。)

我尝试过的一件事是对容易因式分解的数字进行因式分解(使用已知素数的试除法,最多可达一百万)并取消公因式,这有帮助,但还不够。我的错误大约是 1.0E-13。

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

研究:

我看到一篇很好的论文,它讨论了 + - x / 的 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

【问题讨论】:

  • 确实,Python 内置了对高精度算术的支持。为此,我需要在 .Net 中使用扩展精度库,我更愿意避免这样做。

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


【解决方案1】:

什么技术适用于除法?

对于除法a/b,可以求出余数(余数):

a = b*q + r

如果你有 fused-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 不可用怎么办?

我们可以使用 à la Dekker 的精确乘积来模拟 fma,它被分解为 2 个浮点的精确总和,然后使用 Boldo-Melquiond roundToOdd 技巧确保将 3 个浮点的总和精确舍入。

但这将是矫枉过正。我们只使用 fma 来评估残差,因此我们通常使 c 非常接近 -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 的语言可能更聪明...

【讨论】:

  • r = fma(q0*rb,r0-ra); 中的 fma() 调用只有两个而不是必需的三个操作数,我建议仔细检查该行。
  • 从另一个问题,我了解到 FMA 在 C# 中不可用。
  • @PaulChernoch 在这种情况下我们仍然可以模仿它,我完成了答案
【解决方案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 函数应用于运行积的高部分来完成的,然后通过将两个分量除以两个相同的幂来归一化运行积(跟踪运行积的总幂)可以用所需宽度的整数变量在一边进行划分)。

应用frexp 的频率取决于您对所乘整数的限制。如果整数低于 253,这将有助于它们精确地表示为 double 值,您可以在必须对正在运行的乘积进行规范化之前进行大约 19 次乘法运算,因为双精度指数是高达 1023。

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

不要忘记你在分子和分母旁边留下的二的幂!用ldexp 函数减去它们并将差值应用于商。

【讨论】:

  • @PaulChernoch 如果您所说的“一种产品与另一种产品的比率”实际上是趋向于一个的比率的乘积,例如“无限产品”示例,则从分子和分母可以加到同一个变量中,也可以从同一个变量中减去,然后一个简单的int 就足够了。为了做到这一点,以相同的节奏计算分子乘积和分母乘积。
  • 我使用无限产品作为单元测试,这些术语趋向于一个。但我的预期用法涉及的术语产品可能并不总是倾向于一个。
【解决方案3】:

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

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

【讨论】:

  • 我不知道 C# 如何处理 FMA。如果我按照它可以识别的顺序排列我的操作,也许抖动可以执行优化。
【解决方案4】:

如果您可以访问 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,这接近于我用幼稚方法得到的结果。

【讨论】:

  • 您不需要 FMA 来计算 Mul122,只有拥有 FMA 才能更高效。但与 log 相比,无 FMA 的 Mul122 的低效率相形见绌。
猜你喜欢
  • 2021-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-07
  • 1970-01-01
  • 2018-11-15
相关资源
最近更新 更多