22

我对以下 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 单精度浮点格式精确表示而不会损失精度。

4

5 回答 5

11

在这两种情况下,代码都试图从某种整数类型转换floatdouble..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.2 8 使用浮点操作数的运算值和经过通常算术转换的值以及浮点常量的值被评估为范围和精度可能大于类型要求的格式。评估格式的使用以FLT_EVAL_METHOD的实现定义值为特征:

-1 不确定;

0 仅根据类型的范围和精度评估所有操作和常量;

1 评估类型的运算和常量以及类型floatdouble范围和精度double,评估long double运算和常量的范围和类型的精度long double`;

2 根据类型的范围和精度评估所有操作和常量long double


C11dr §5.2.4.2.2 9 除了赋值和强制转换(删除所有额外的范围和精度)之外,由具有浮动操作数的运算符产生的值和经过通常算术转换的值和浮动常量的值被评估为一种格式,其范围并且精度可能大于类型所要求的。评估格式的使用以FLT_EVAL_METHOD的实现定义值为特征

-1(与 C99 相同)

0(同C99)

1(同C99)

2(同C99)

于 2014-11-24T22:16:56.767 回答
7

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

  • 类型具有一组可表示的值(隐含)
  • 所有可表示的值float也可表示为double (6.2.5/10)
  • 转换floatdouble不会更改值 (6.3.1.5/1)
  • 转换intfloat,当 int 值在 的可表示值集中时float,会​​给出该值。
  • 转换intfloat,当 int 值的大小小于FLT_MAX并且int不是 的可表示值时float,会导致选择下一个最高值或下一个最低float值,并且选择哪个值是实现定义的。(6.3.1.4/2)

这些点中的第三点保证float提供给的值printf不会被默认参数提升修改。

如果2147483647在 中可以表示float,那么(float)x并且(float)2147483647必须给2147483647.000000

如果2147483647在 中不可表示float,则(float)x并且(float)2147483647必须给出下一个最高或下一个最低的值float。他们不必都做出相同的选择。但这意味着2147483647.000000不允许打印1,每个必须是较高值或较低值。


1好吧 - 从理论上讲,下一个最低浮点数可能是2147483646.9999999...这样,当该值以 6 位精度显示时,printf它会被四舍五入以给出所看到的内容。但这在 IEEE754 中并非如此,您可以轻松地尝试排除这种可能性。

于 2014-11-24T22:58:37.357 回答
2

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

于 2014-11-24T20:27:40.480 回答
0

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

您还会注意到 gcc-3.4.2 来自 2004 年。实际上,您使用的是 32 位编译器。x86 上的 gcc与浮点数学运算相当快速和松散FLT_EVAL_METHOD如果 gcc 设置为非零值,这可能在技术上被 C 标准证明是合理的。

于 2014-11-24T22:24:50.353 回答
-1

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

http://ideone.com/Ssw8GR

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

http://ideone.com/OGypBC

(使用显式浮点转换)。

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

它也可能与偏好有关printf

于 2014-11-24T22:28:58.757 回答