将int转换为在C中浮动时的奇怪行为
我对以下C程序的输出有疑问。 我尝试使用Visual C ++ 6.0和MinGW32(gcc 3.4.2)编译它。
#include int main() { int x = 2147483647; printf("%f\n", (float)2147483647); printf("%f\n", (float)x); return 0; }
输出是:
2147483648.000000 2147483647.000000
我的问题是:为什么两条线都不同? 将整数值2147483647转换为IEEE 754浮点格式时,它将近似为2147483648.0。 所以,我预计这两行都将等于2147483648.000000。
编辑 :值“2147483647.000000”不能是单精度浮点值,因为数字2147483647无法精确表示IEEE 754单精度浮点格式而不会丢失精度。
在这两种情况下,代码都试图从某种整数类型转换为float
然后再转换为double
。 double
转换发生,因为它是传递给可变函数的float
值。
检查FLT_EVAL_METHOD
的设置,怀疑它的值为1或2(OP报告2
至少有一个编译器)。 这允许编译器评估float
“…操作和常量到范围和精度”大于float
。
您的编译器优化(float)x
直接将int
为double
算术。 这是运行期间的性能改进。
(float)2147483647
是一个编译时间转换,并且编译器针对int
优化float
到double
精度,因为性能在这里不是问题。
[Edit2]有趣的是,C11规范比C99规范更具体,增加了“除了赋值和转换……”。 这意味着C99编译器有时允许int
进行double
直接转换,而不首先通过float
并且C11被修改为显然不允许跳过转换。
由于C11正式排除了这种行为,现代的编制者不应该这样做,但是较旧的,比如OP可能 – 因此是C11标准的错误。 除非发现某些其他C99或C89规范另有说明,否则这似乎是允许的编译器行为。
[编辑]由@Keith Thompson,@ tmyklebu,@ Matt McNabb一起评论编译器,即使是非零FLT_EVAL_METHOD
,也应该产生2147483648.0...
因此,编译器优化标志明确地覆盖了正确的行为,或者编译器有一个角落错误。
C99dr§5.2.4.2.28具有浮动操作数的操作值和通常算术转换以及浮动常量的值将被评估为其范围和精度可能大于该类型所需的格式。 评估格式的使用以FLT_EVAL_METHOD的实现定义值为特征 :
-1不确定;
0仅根据类型的范围和精度评估所有操作和常量;
1计算float
类型的操作和常量以及double
类型的范围和精度,将long double
操作和常量计算为long double
类型的范围和精度。
2评估long double
类型的范围和精度的所有操作和常量。
C11dr§5.2.4.2.29除了赋值和强制转换(删除所有额外的范围和精度)之外,具有浮动操作数的运算符产生的值和通常算术转换以及浮点常量的值将被评估为其范围的格式和精度可能大于该类型的要求。 评估格式的使用以FLT_EVAL_METHOD的实现定义值为特征
-1(与C99相同)
0(与C99相同)
1(与C99相同)
2(与C99相同)
这肯定是一个编译器错误。 根据C11标准,我们有以下保证(C99类似):
- 类型具有一组可表示的值(隐含)
-
float
表示的所有值也可以表示为double
(6.2.5 / 10) - 将
float
转换为double
不会更改值(6.3.1.5/1) - 将
int
为float
,当int值在float
的可表示值集合中时,给出该值。 - 将
int
为float
,当int值的大小小于FLT_MAX
并且int
不是float
的可表示值时,会导致选择下一个最高或下一个最低float
值,选择哪一个是实现-defined。 (6.3.1.4/2)
这些点中的第三个保证提供给printf
的float
值不会被默认参数提升修改。
如果2147483647
在float
可表示,则(float)x
和(float)2147483647
必须给出2147483647.000000
。
如果2147483647
在float
不可表示,那么(float)x
和(float)2147483647
必须给出下一个最高或下一个最低float
。 他们不必同时做出相同的选择。 但这意味着不允许打印输出2147483647.000000
1 ,每个都必须是更高的值或更低的值。
1嗯 – 从理论上讲,下一个最低的浮点数可能是2147483646.9999999...
所以当printf
以6位精度显示该值时,它会被舍入以显示所看到的内容。 但在IEEE754中并非如此,您可以轻松地尝试折扣这种可能性。
在第一个printf
,编译器完成从整数到浮点的转换。 在第二个,它由C运行时库完成。 没有特别的理由说明为什么他们应该在精确度的极限下产生相同的答案。
Visual C ++ 6.0于上个世纪发布,我相信它早于标准C ++。 VC ++ 6.0表现出破碎的行为,这一点完全不足为奇。
你还会注意到gcc-3.4.2是2004年的。事实上,你使用的是32位编译器。 x86上的gcc 使用浮点数学运算速度相当快且松散 。 如果gcc将FLT_EVAL_METHOD
设置为非零值,则技术上可以通过C标准certificate这一点。
你们中的一些人说这是一个优化错误,但我有点不同意。 我认为这是一个合理的浮点精度误差,并且是一个向人们展示浮点如何工作的好例子。
也许OP可能会尝试将我的程序粘贴到您的计算机中,并尝试使用您的编译器进行编译,看看会发生什么。 或尝试:
(使用显式浮点转换)。
无论如何,如果我们计算出错误,它就是4.656612875245797e-10
那么多,应该被认为是非常精确的。
它也可能与printf
的偏好有关。