考虑两个程序。第一个在gcc 5.3.0上打印“Unequal”(目标:i686-pc-cygwin)。使用-ansi选项时会打印“Equal”。
int main () {
double d = 2.335 - 2.334;
double q = 0.001;
if (d == q) {
printf ("Equal\n");
} else {
printf ("Unequal\n");
}
return 0;
}
第二个使用或不使用-ansi选项打印“Unequal”。
int main () {
if (2.335 - 2.334 == 0.001) {
printf ("Equal\n");
} else {
printf ("Unequal\n");
}
return 0;
}
差异的根源是什么? 当然,众所周知,不应对实数进行相等性测试。我理解IEEE754标准对涉及浮点的计算(im-)精度的影响。但是,据我所知,这两个程序在语义上应该是等效的,并给出相同的结果。
在C99模式的第一个中是否有一些隐式转换在C99中删除了?
答案 0 :(得分:2)
C99和C11精确定义了当主机平台只能方便地计算到比float
和double
更高的精度时会发生什么。早期的C89(或“ANSI”)C标准没有。在C99或C11中,编译器将FLT_EVAL_METHOD
定义为1或2,它告诉程序员浮点常量和操作将被解释为比其类型更高的精度。
这是在this message中讨论的补丁中的GCC中实现的。
默认情况下,补丁提供的选项-fexcess-precision=standard
在C99和C11中启用,但在“ANSI”(C89)模式下未启用。
尝试解释编译器在C89模式下的作用并没有多大意义:它有点模糊,浮点变量的值在没有赋值的情况下发生变化,或者在优化级别之间变化,如this report中所述。在C99模式下,编译器将FLT_EVAL_METHOD
定义为2
,编译器将差值2.335 - 2.334
计算为80位浮点数,即80位之间的差值FP表示2335/1000和80位FP表示2334/1000。这个数字恰好与1/1000的80位表示不同。这就是为什么测试程序的第二个版本与没有-ansi
的情况一样。在测试程序的第一个版本中,对double
变量的赋值会导致数字四舍五入为双精度(64位)浮点值。因此,它们在被舍入后都是相等的。
答案 1 :(得分:0)
警告:这可能不是一个完整的答案,为什么,但这里的一些数据基于编译各种选项和反汇编输出......
第二个程序[仅使用文字常量] 不生成任何浮点指令。它只做一个printf
。 (即所有计算都在编译器内完成。)
因此,以下仅限于第一个程序。
您没有指定您为gcc
提供的[其他]命令行选项,但我只想到-ansi
。
我有一台64位的linux机器[gcc
5.3.1],所以我不得不添加-m32
。
没有它,反汇编[64位],有或没有-ansi
是相同并产生Unequal
[它使用XMM
指令] 。使用-O2
,生成无浮点指令,只需printf
[有或没有-ansi
]。同样,也是如此。
如果我使用-m32
和-O2
,则只会生成printf
且输出为Unequal
,无论-ansi
是否
唯一的差异发生在32位,优化关闭和使用-ansi
之间。在这里,gcc
生成旧/传统387 FP协处理器单元的指令。
没有-ansi
,这里是反汇编:
dyn.o: file format elf32-i386
Disassembly of section .text:
00000000 <main>:
0: 8d 4c 24 04 lea 0x4(%esp),%ecx
4: 83 e4 f0 and $0xfffffff0,%esp
7: ff 71 fc pushl -0x4(%ecx)
a: 55 push %ebp
b: 89 e5 mov %esp,%ebp
d: 51 push %ecx
e: 83 ec 14 sub $0x14,%esp
11: dd 05 10 00 00 00 fldl 0x10
17: dd 5d f0 fstpl -0x10(%ebp)
1a: dd 05 18 00 00 00 fldl 0x18
20: dd 5d e8 fstpl -0x18(%ebp)
23: dd 45 f0 fldl -0x10(%ebp)
26: dd 45 e8 fldl -0x18(%ebp)
29: df e9 fucomip %st(1),%st
2b: dd d8 fstp %st(0)
2d: 7a 1e jp 4d <L00>
2f: dd 45 f0 fldl -0x10(%ebp)
32: dd 45 e8 fldl -0x18(%ebp)
35: df e9 fucomip %st(1),%st
37: dd d8 fstp %st(0)
39: 75 12 jne 4d <L00>
3b: 83 ec 0c sub $0xc,%esp
3e: 68 00 00 00 00 push $0x0
43: e8 fc ff ff ff call 44 <main+0x44>
48: 83 c4 10 add $0x10,%esp
4b: eb 10 jmp 5d <L01>
4d:L00 83 ec 0c sub $0xc,%esp
50: 68 06 00 00 00 push $0x6
55: e8 fc ff ff ff call 56 <main+0x56>
5a: 83 c4 10 add $0x10,%esp
5d:L01 b8 00 00 00 00 mov $0x0,%eax
62: 8b 4d fc mov -0x4(%ebp),%ecx
65: c9 leave
66: 8d 61 fc lea -0x4(%ecx),%esp
69: c3 ret
对于-ansi
,单个指令与相同
--- dynstd.dis 2016-06-09 09:58:18.719906988 -0700
+++ dynansi.dis 2016-06-09 09:58:44.266286688 -0700
@@ -14,7 +14,7 @@
e: 83 ec 14 sub $0x14,%esp
11: dd 05 10 00 00 00 fldl 0x10
17: dd 5d f0 fstpl -0x10(%ebp)
- 1a: dd 05 18 00 00 00 fldl 0x18
+ 1a: dd 05 10 00 00 00 fldl 0x10
20: dd 5d e8 fstpl -0x18(%ebp)
23: dd 45 f0 fldl -0x10(%ebp)
26: dd 45 e8 fldl -0x18(%ebp)
请注意,差异略高于fldl 0x10
。然后,如果没有-ansi
,则后跟fldl 0x18
。使用-ansi
,后跟fldl 0x10
。因此,如果没有-ansi
[我最好的猜测],我们正在比较0x10 == 0x18
[不平等]和-ansi
我们正在比较0x10 == 0x10
[等于]
几乎作为旁注,我使用clang
重复了相同的测试,但即使使用-m32
,它也会生成XMM
条指令,反汇编是相同的,输出始终是Unequal
。
所以,AFAICT,对于一组有限的选项,这可能是gcc
的代码生成问题(即错误)。