我正在尝试计算该float
类型的epsilon值的近似值(我知道它已经在标准库中).
这台机器上的epsilon值是(用一些近似值打印):
FLT_EPSILON = 1.192093e-07
DBL_EPSILON = 2.220446e-16
LDBL_EPSILON = 1.084202e-19
Run Code Online (Sandbox Code Playgroud)
FLT_EVAL_METHOD
被2
所以一切都在做long double
精度,和float
,double
和long double
是32,64和96位.
我试图从1开始得到一个近似的值并将其除以2,直到它变得太小,用float
类型执行所有操作:
# include <stdio.h>
int main(void)
{
float floatEps = 1;
while (1 + floatEps / 2 != 1)
floatEps /= 2;
printf("float eps = %e\n", floatEps);
}
Run Code Online (Sandbox Code Playgroud)
输出不是我想要的:
float epsilon = 1.084202e-19
Run Code Online (Sandbox Code Playgroud)
中间操作以最高精度(由于其值FLT_EVAL_METHOD
)完成,因此该结果似乎是合法的.
但是,这个:
// 2.0 is a double literal
while ((float) (1 + floatEps / 2.0) != 1)
floatEps /= 2;
Run Code Online (Sandbox Code Playgroud)
给出这个输出,这是正确的:
float epsilon = 1.192093e-07
Run Code Online (Sandbox Code Playgroud)
但是这一个:
// no double literals
while ((float) (1 + floatEps / 2) != 1)
floatEps /= 2;
Run Code Online (Sandbox Code Playgroud)
再次导致错误的结果,作为第一个:
float epsilon = 1.084202e-19
Run Code Online (Sandbox Code Playgroud)
这最后两个版本在这个平台上应该是等价的,这是一个编译器错误吗?如果没有,发生了什么?
代码编译为:
gcc -O0 -std=c99 -pedantic file.c
Run Code Online (Sandbox Code Playgroud)
gcc版本很老了,但是我在大学时无法更新它:
$ gcc -v
Using built-in specs.
Target: i486-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.4.5-8'
--with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs
--enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.4
--enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib
--libexecdir=/usr/lib --without-included-gettext --enable-threads=posix
--with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib --enable-nls
--enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc
--enable-targets=all --with-arch-32=i586 --with-tune=generic
--enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu
--target=i486-linux-gnu
Thread model: posix
gcc version 4.4.5 (Debian 4.4.5-8)
Run Code Online (Sandbox Code Playgroud)
当前版本的gcc 4.7在我的家用计算机上运行正常.还有评论说不同的版本会给出不同的结果.
在得到一些答案和评论之后,我澄清了什么是预期的行为,什么不是,我改变了一些问题以使其更清楚.
允许编译器float
以任何更大的精度计算表达式,因此看起来第一个表达式是以long double
精度计算的.在第二个表达式中,您可以强制将结果float
再次缩放.
在回答您的一些其他问题和下面的讨论时:您基本上是在寻找一些浮点类型中最小的非零差异.根据FLT_EVAL_METHOD
编译器的设置,可能决定以比所涉及的类型更高的精度评估所有浮点表达式.在奔腾上,传统上浮点单元的内部寄存器是80位,并且对于所有较小的浮点类型使用该精度很方便.所以最后你的测试取决于比较的精确度!=
.在没有显式强制转换的情况下,此比较的精度由编译器确定,而不是由您的代码确定.通过显式转换,您可以将比较缩小到您想要的类型.
正如您所确认的那样,您的编译器已设置FLT_EVAL_METHOD
为,2
因此它对任何浮点计算使用最高精度.
作为下面讨论的结论,我们有信心地说,FLT_EVAL_METHOD=2
在gcc
版本4.5之前存在与案例实现相关的错误,并且至少从版本4.6修复了该错误.如果2
在表达式中使用整数常量而不是浮点常量2.0
,float
则在生成的程序集中省略强制转换.值得注意的是,从优化级别开始-O1
,在这些较旧的编译器上生成了正确的结果,但生成的程序集完全不同,并且只包含很少的浮点运算.