【问题标题】:exp10 different to pow(10)exp10 与 pow(10) 不同
【发布时间】:2014-07-03 05:04:49
【问题描述】:

首先,我意识到大多数以 10 为基数的数字不能以 2 为基数精确表示,所以我的问题并不是关于浮点运算的缺陷。

我正在尝试编写一个函数,该函数将尝试通过检查最后 6 个有意义的数字是否在某个容差范围内并将其更改为高于某个假定的精确值的下一个可表示的数字(仅用于显示目的)来纠正受累积舍入误差影响的双重污染- 除非它是整数或 2 的幂)。

我的函数中令我惊讶的一个组件是 exp10 的输出;据我所知,只要两个双精度之间的间距小于 2,那么存储为双精度的整数值应该是精确的 - 尽管 10^14 正在推动它,这应该是一个精确的整数 em>(因为 10^14 =~ 2^46.507

我的调试工作的摘录(没有什么是显而易见的),输出如下:

double test = 0.000699;
double tmp = fabs(test);
double exp = 10.0 - floor(log10(tmp));
double powTen = exp10(10.0 - floor(log10(tmp)));
double powTen2 = exp10(exp);
double powTen3 = exp10((int)exp);
double powTen4 = exp10(exp);
double powTen5 = pow(10, exp);

printf("exp: %.16lf\n", exp);
printf("powTen: %.16lf\n", powTen);
printf("powTen2: %.16lf\n", powTen2);
printf("powTen3: %.16lf\n", powTen3);
printf("powTen4: %.16lf\n", powTen4);

//these two are exact
printf("10^14: %.16lf\n", exp10(14));
printf("powTen5: %.16lf\n", powTen5);
printf("exp == 14.0: %d\n", exp == 14.0);

输出:

exp: 14.0000000000000000
powTen: 100000000000000.1250000000000000
powTen2: 100000000000000.1250000000000000
powTen3: 100000000000000.1250000000000000
powTen4: 100000000000000.1250000000000000
10^14: 100000000000000.0000000000000000
powTen5: 100000000000000.0000000000000000
exp == 14.0: 1

pow 得到了准确的答案,就像 exp10 带有硬编码的 int 一样。对于所有其他情况,我添加 1/8(10 ^ 14 和 10 ^ 14 之间的间距 + 下一个可表示的是 1/64)。 文档说 exp10 应该等同于 pow。谁能看到我遗漏的东西?

编辑 - 通过 O3、O2、O1 优化,我得到了预期的输出 - 除非直到运行时才能知道数据。此时 exp10 仍然行为不端。

【问题讨论】:

  • 谢谢我一直在关注那篇文章,但是 exp10 的这种行为是不正确的——除非我对它的使用不正确——我不是在问为什么 0.6 看起来像 0.5999999999.... + junk或者为什么 0.3 - 0.2 -0.1 ! = 0.0 依此类推...我在问为什么 can 可以完全表示为整数而不是用 exp10 表示,但 is 用 pow
  • exp10(14) 可能正在由编译器评估,它可能具有不同的舍入设置。其他的无法解释。
  • 顺便说一句,请打印exp == 14.0的结果
  • 由于这些都是编译时常量,经过优化,它们可能都是在编译期间计算出来的。

标签: c++ floating-point math.h


【解决方案1】:

很可能您的exp10 实现行为不端。请注意,它返回的结果有时会偏离 ulp(相对于 10^14 为 0.125。)

这是一个相当令人发指的错误;您有一个案例,正确答案可以表示为 doubleexp10 没有这样做。

我会回应 Ben Voigt 的评论,即编译器有时可能会自行评估事物,而不是将它们传递给数学库。它可能做得更好,因为它可能链接到任意精度的数学库。您可以尝试使用 -fno-builtin 选项,看看它是否有任何改变。

不幸的是,我认为crlibm 没有实现exp10。否则,我建议您使用它并停止担心。

编辑:我拥有的eglibc 源的副本似乎实现了exp10,因此:

double
__ieee754_exp10 (double arg)
{
  /* This is a very stupid and inprecise implementation.  It'll get
     replaced sometime (soon?).  */
  return __ieee754_exp (M_LN10 * arg);
}

不要指望这会很好。

【讨论】:

  • @MarkRansom 0.5 ULP 是“完美的”。 1 ULP 为中间结果留出了足够的空间,每个结果都会稍有错误,只要您可以稍微扩展精度来计算它们。如果我必须将 exp10 实现为 1 个 ULP,我会调用质量差的 exp10l()(一些 ULP 错误)并将结果四舍五入为 double。如果我必须实现exp10,那就更难了。我必须首先查找了解 Sollya 的人住在哪里,然后绑架他们的狗。会很乱。
  • @MarkRansom:我不会追求完美的结果,但 1 ulp 的错误确实令人发指。我将如何实现 exp10 以获得 sub-ulp 错误:在 [1,2] 上找到一个很好的近似多项式到四精度,使用 double double 技术将其评估为扩展精度,处理指数(作为 double double),并将两者相乘(再次作为double double)。也许我会用有效数字的高位和指数做类似的把戏。如果做得好,您最多可以获得 0.51 ulp 的保证。
  • 你们这些人,你们的野蛮矫枉过正。双双?四边形?呸。 exp10 具有出色的级数展开式,您可以通过“Gal 的精确表”和 Chebyschev、Cathedory-Fejer 或 Minimax 多项式对以原生双精度评估的残差轻松提供亚 ulp 精度。试试吧,这是度过一个下午的有趣方式。
  • @StephenCanon:认为我是书呆子。
  • 仅供参考,最坏情况下 exp10 在双精度(binary64)中舍入为:exp10(1.A83B1CF779890P-26),其精确结果为 1.000000F434FAAP0:01[60]0101 ...(冒号后面是截断的 53 位有效数字之后的位序列,[60] 表示前面的位 1 重复 60 次)。因此,需要大约 114 位的精度。完整结果:vinc17.net/research/slides/tamadi2010-10.pdf
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-04-29
  • 1970-01-01
  • 1970-01-01
  • 2020-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多