我不想在不准确的值会造成困扰时引入浮点数,所以我对你何时可以安全地使用它们有几个问题。
只要不溢出有效数字的数量,它们对整数是否精确?这两个测试总是正确的:
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)所有浮点运算符和函数都保证结果最接近实际数学结果吗?
答案 0 :(得分:6)
我也发现不正确的结果令人反感。
在常见硬件上,您可以依靠+
,-
,*
,/
和sqrt
工作并提供正确舍入的结果。也就是说,它们提供最接近其参数或参数的和,差,乘积,商或平方根的浮点数。
某些库函数,尤其是log2
和log10
以及exp2
和exp10
,传统上都有可怕的实现,甚至都没有忠实地实现。忠实圆形意味着函数提供两个浮点数中的一个包围确切结果。大多数现代pow
实现都有类似的问题。很多这些功能甚至可以打击log10(10000)
和pow(7, 2)
等确切情况。因此,即使在确切的情况下,涉及这些功能的相等比较也会产生麻烦。
sin
,cos
,tan
,atan
,exp
和log
在每个平台上都有忠实的实现最近遇到过。在过去的糟糕时期,在使用x87 FPU评估sin
,cos
和tan
的处理器上,您会得到可怕的错误输出,并且您会得到输入对于更大的投入。 CRlibm具有正确的舍入实现;这些都不是主流,因为据我所知,他们的情况比传统的忠实实现更糟糕。
copysign
和nextafter
以及isfinite
之类的内容都能正常运行。 ceil
和floor
以及rint
和朋友总能提供准确的结果。 fmod
和朋友也一样。 frexp
和朋友一起工作。 fmin
和fmax
工作。
有人认为通过计算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)
嗯,他们可以存储适合其尾数(有效数字)的整数。所以[-2 ^ 53,2 ^ 53]为双倍。有关详情,请参阅:Which is the first integer that an IEEE 754 float is incapable of representing exactly?
他们至少保证结果立即出现在实际数学结果的两边。也就是说,您将无法获得在其自身与“实际”结果之间具有有效浮点值的结果。但请注意,因为重复操作可能会累积一个与此相反的错误,而不是(因为所有中间值都受到相同的约束,而不仅仅是复合表达式的输入和输出)。