将int转换为在C中浮动时的奇怪行为

时间:2014-11-24 19:58:00

标签: c casting floating-point visual-studio-6 gcc3

我对以下C程序的输出有疑问。我尝试使用Visual C ++ 6.0和MinGW32(gcc 3.4.2)编译它。

#include <stdio.h>

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单精度浮点中精确表示格式不会丢失精确度。

5 个答案:

答案 0 :(得分:11)

在这两种情况下,代码都试图从某种整数类型转换为float然后再转换为double .. double转换发生,因为它是float传递的值一个可变函数。

检查FLT_EVAL_METHOD的设置,怀疑它的值为1或2(OP报告2至少有一个编译器)。这允许编译器评估float“...操作和常量到范围和精度”大于float

您的编译器优化(float)x直接intdouble算术。这是运行期间的性能改进。

(float)2147483647是一个编译时间,编译器针对int优化到floatdouble的准确性,因为这里的性能不是问题。


[Edit2]有趣的是,C11规范比C99规范更具体,增加了“除了赋值和转换......”。这意味着C99编译器有时允许intdouble直接转换,而不首先通过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评估floatdouble类型的操作和常量到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相同)

答案 1 :(得分:7)

这肯定是编译器错误。根据C11标准,我们有以下保证(C99类似):

  • 类型具有一组可表示的值(隐含)
  • float所代表的所有值也可由double表示(6.2.5 / 10)
  • float转换为double不会更改值(6.3.1.5/1)
  • int投射到float,当int值位于float的可表示值集合中时,会给出该值。
  • int投射到float,当int值的幅度小于FLT_MAXint不是float的可表示值时,导致选择下一个最高或下一个最低float值,选择哪一个是实现定义的。 (6.3.1.4/2)

这些要点中的第三个保证提供给float的{​​{1}}值不会被默认参数促销修改。

如果printf中可以表示2147483647,则float(float)x必须提供(float)2147483647

如果在2147483647.000000中无法表示2147483647,则float(float)x必须提供次高或次低(float)2147483647。他们不必做出相同的选择。但这意味着不允许打印输出float 1 ,每个都必须是更高的值或更低的值。


1 嗯 - 从理论上讲,下一个最低的浮点数是2147483647.000000,所以当值{6}精度显示2147483646.9999999...时它是四舍五入的,给出了所见所闻。但是在IEEE754中这并不正确,你可以轻松地试验这种可能性。

答案 2 :(得分:2)

在第一个printf上,编译器完成从整数到浮点的转换。在第二个,它由C运行时库完成。没有特别的理由说明为什么他们应该在精确度的极限下产生相同的答案。

答案 3 :(得分:0)

Visual C ++ 6.0于上个世纪发布,我相信它早于标准C ++。 VC ++ 6.0表现出破碎的行为,这完全不足为奇。

你还会注意到gcc-3.4.2是2004年的。实际上,你使用的是32位编译器。 gcc on x86 plays rather fast and loose with floating-point math。如果gcc将FLT_EVAL_METHOD设置为非零值,则技术上可以通过C标准证明这一点。

答案 4 :(得分:-1)

你们中的一些人说这是一个优化错误,但我有点不同意。我认为这是一个合理的浮点精度误差,也是一个向人们展示浮点如何工作的好例子。

http://ideone.com/Ssw8GR

也许OP可能会尝试将我的程序粘贴到您的计算机中并尝试使用您的编译器进行编译,看看会发生什么。或尝试:

http://ideone.com/OGypBC

(使用显式浮点转换)。

无论如何,如果我们计算错误,那么4.656612875245797e-10那么多,应该被认为是非常精确的。

它也可能与printf的偏好有关。