【问题标题】:How unreliable are floating point values, operators and functions?浮点值、运算符和函数有多不可靠?
【发布时间】:2014-09-27 03:09:47
【问题描述】:

我不想在不精确的值会造成灾难时引入浮点,所以我有几个问题是关于何时可以安全地使用它们。

只要不溢出有效数字的数量,它们对整数是否准确?这两个测试是否总是正确的:

double d = 2.0;
if (d + 3.0 == 5.0) ...
if (d * 3.0 == 6.0) ...

您可以依赖哪些数学函数?这些测试是否总是正确的:

#include <math.h>

double d = 100.0;
if (log10(d) == 2.0) ...
if (pow(d, 2.0) == 10000.0) ...
if (sqrt(d) == 10.0) ...

这个怎么样:

int v = ...;
if (log2((double) v) > 16.0) ... /* gonna need more than 16 bits to store v */
if (log((double) v) / log(2.0) > 16.0) ... /* C89 */

我想您可以将这个问题总结为:1) 浮点类型能否将所有整数的精确值保存到 float.h 中的有效数字位数? 2) 是否所有浮点运算符和函数都保证结果与实际数学结果最接近?

【问题讨论】:

  • 在 Wikipedia 和网络上的其他地方有一些关于浮点的非常好的文章。您应该阅读其中的一些内容。
  • 整数值被精确存储,直到某个最大值。第一个块将完全按照您期望的方式进行评估。我不知道你其他问题的答案。附言整数的最大值将略小于有效位数。
  • @MarkRansom - 整数是精确的在 IEEE 浮点数中。这可能不适用于其他浮点格式,C 没有指定要使用的浮点格式。
  • @HotLicks 这就是为什么我按照我的方式措辞,没有指定确切的值或位数。您是否知道 任何 浮点格式,无论多么晦涩难懂,都不能保存精确的整数?

标签: c floating-point


【解决方案1】:

我也觉得不正确的结果令人讨厌。

在通用硬件上,您可以依靠 +-*/sqrt 工作并提供正确舍入的结果。也就是说,它们提供最接近其参数的和、差、积、商或平方根的浮点数。

一些库函数,特别是 log2log10exp2exp10,传统上具有糟糕的实现,甚至没有忠实地完成。忠实四舍五入意味着一个函数提供了包含精确结果的两个浮点数之一。大多数现代pow 实现都有类似的问题。许多这些功能甚至会破坏 log10(10000)pow(7, 2) 这样的确切情况。因此,即使在确切的情况下,涉及这些函数的相等比较也是自找麻烦。

sincostanatanexplog 在我最近遇到的每个平台上都有忠实全面的实现。在过去糟糕的日子里,在使用 x87 FPU 评估 sincostan 的处理器上,对于较大的输入,您会得到非常错误的输出,而对于较大的输入,您会得到返回的输入。 CRlibm 具有正确舍入的实现;这些不是主流,因为据我所知,它们的最坏情况比传统的忠实全面的实现更糟糕。

copysignnextafterisfinite 之类的东西都可以正常工作。 ceilfloorrint 和朋友总是提供准确的结果。 fmod 和朋友也一样。 frexp 和朋友一起工作。 fminfmax 工作。

有人认为通过将x*y 舍入为double,然后添加z 并将结果舍入为double,使fma(x,y,z) 计算x*y+z 将是一个绝妙的主意。您可以在现代平台上找到这种行为。这很愚蠢,我讨厌它。

我对 C 库中的双曲三角函数、伽马函数或贝塞尔函数没有经验。

我还应该提到,针对 32 位 x86 的流行编译器使用不同的、被破坏的规则集。由于 x87 是唯一受支持的浮点指令集,并且所有 x87 算术都是使用扩展指数完成的,因此会导致双精度下溢或溢出的计算可能无法下溢或溢出。此外,由于默认情况下 x87 也使用扩展有效位,因此您可能无法获得所需的结果。更糟糕的是,编译器有时会将中间结果溢出到精度较低的变量中,因此您甚至不能依赖 doubles 以扩展精度完成的计算。 (Java 有一个 trick 用于使用 80 位寄存器进行 64 位数学运算,但它非常昂贵。)

如果您的目标是 32 位 x86,我建议您坚持使用 long doubles 上的算术。编译器应该将FLT_EVAL_METHOD 设置为适当的值,但我不知道这是否普遍。

【讨论】:

  • 对于sincos,结果与大输入的真实值完全无关的情况并不少见。例如,英特尔的 x87 fcosfsin 指令只有在假设 pi 完全等于英特尔使用的 66 位近似值时才是“准确的”。
  • @MarkDickinson:谢谢。我一定是在屏蔽这些记忆。多么可怕的指示。
  • @MarkDickinson:在什么非人为的场景中会要求输入 sin(X) where |x| > 3.0 对 x 的四分之一 LSB 内的任何值的正弦不同样满意?大多数要求 sin(x) 的代码确实需要一些有理数常数 k 和一些变量 y 的 sin(kπy) 角度。精确计算需要先发制人的参数减少;如果 sin(x) 函数传递的值大于 3,这似乎暗示调用代码已经接受了不必要的舍入错误,并且不太可能关心 sub-LSB 精度。
  • @tmyklebu:可怕的是使用弧度而不是象限或圆作为三角函数的角度单位。当 180 度的正弦应该为 ZERO 时,抱怨英特尔没有为 (180.0*(Math.PI/180.0)) 的正弦报告足够大的值是愚蠢的。
  • @supercat:嗯?弧度是测量角度的好方法。欢迎您实现自己的sindeg 或者如果您真的想要这样的东西,您可以使用whathaveyou。问题是,正确地为弧度三角函数做参数减少并不是很难。
【解决方案2】:
  1. 浮点类型能否将所有整数的精确值保持到 float.h 中的有效数字位数?

好吧,他们可以存储适合尾数(有效数字)的整数。所以 [-2^53, 2^53] 是双倍的。有关更多信息,请参阅:Which is the first integer that an IEEE 754 float is incapable of representing exactly?

  1. 是否所有浮点运算符和函数都保证结果与实际数学结果最接近?

他们至少保证结果立即位于实际数学结果的任一侧。也就是说,您不会得到在其自身和“实际”结果之间具有有效浮点值的结果。但要注意,因为重复的操作可能会累积一个错误,这似乎与此相反,但事实并非如此(因为所有中间值都受到相同的约束,而不仅仅是复合表达式的输入和输出)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-28
    • 2021-09-26
    • 1970-01-01
    相关资源
    最近更新 更多