浮点值,运算符和函数有多不可靠?

时间:2014-09-27 03:09:48

标签: c floating-point

我不想在不准确的值会造成困扰时引入浮点数,所以我对你何时可以安全地使用它们有几个问题。

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

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)所有浮点运算符和函数都保证结果最接近实际数学结果吗?

2 个答案:

答案 0 :(得分:6)

我也发现不正确的结果令人反感。

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

某些库函数,尤其是log2log10以及exp2exp10,传统上都有可怕的实现,甚至都没有忠实地实现。忠实圆形意味着函数提供两个浮点数中的一个包围确切结果。大多数现代pow实现都有类似的问题。很多这些功能甚至可以打击log10(10000)pow(7, 2)等确切情况。因此,即使在确切的情况下,涉及这些功能的相等比较也会产生麻烦。

sincostanatanexplog在每个平台上都有忠实的实现最近遇到过。在过去的糟糕时期,在使用x87 FPU评估sincostan的处理器上,您会得到可怕的错误输出,并且您会得到输入对于更大的投入。 CRlibm具有正确的舍入实现;这些都不是主流,因为据我所知,他们的情况比传统的忠实实现更糟糕。

copysignnextafter以及isfinite之类的内容都能正常运行。 ceilfloor以及rint和朋友总能提供准确的结果。 fmod和朋友也一样。 frexp和朋友一起工作。 fminfmax工作。

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

我对C库中的双曲线trig,gamma或Bessel函数没有经验。

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

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

答案 1 :(得分:3)

  1. 浮点类型是否可以保存所有整数的精确值,直到float.h中有效数字的数字?
  2. 嗯,他们可以存储适合其尾数(有效数字)的整数。所以[-2 ^ 53,2 ^ 53]为双倍。有关详情,请参阅:Which is the first integer that an IEEE 754 float is incapable of representing exactly?

    1. 所有浮点运算符和函数都保证结果最接近实际数学结果吗?
    2. 他们至少保证结果立即出现在实际数学结果的两边。也就是说,您将无法获得在其自身与“实际”结果之间具有有效浮点值的结果。但请注意,因为重复操作可能会累积一个与此相反的错误,而不是(因为所有中间值都受到相同的约束,而不仅仅是复合表达式的输入和输出)。