有和没有-ansi差异的gcc浮点数
gcc floating point with and without -ansi disparity
考虑这两个程序。第一个在 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;
}
第二个打印 "Unequal" 有或没有 -ansi 选项。
int main () {
if (2.335 - 2.334 == 0.001) {
printf ("Equal\n");
} else {
printf ("Unequal\n");
}
return 0;
}
差异的根源是什么?
当然,实数不应该进行相等性测试是常识。我了解 IEEE754 标准对涉及浮点计算的(不)精度的影响。然而,据我所知,这两个程序在语义上应该是等价的,并且给出相同的结果。
在 C99 中删除的第一个 C89 模式中是否进行了一些隐式转换?
警告:这可能不是关于原因的完整答案,但这里有一些基于使用各种选项编译和反汇编输出的数据...
第二个程序[仅使用文字常量]不生成任何浮点指令。它执行单个 printf
。 (即所有计算都在编译器内完成)。
所以,以下仅限于第一个节目。
您没有指定给 gcc
的 [其他] 命令行选项,但我想只有 -ansi
。
我有一台 64 位 linux 机器 [gcc
5.3.1],所以我必须添加 -m32
.
没有它,反汇编 [for 64 bit],有或没有 -ansi
都是 same 并产生 Unequal
[它使用 XMM
说明]。使用 -O2
, 不会生成 浮点指令,只是一个 printf
[有或没有 -ansi
]。还是一样。
如果我使用-m32
和-O2
,只生成printf
,输出是Unequal
,不管-ansi
与否
唯一的差异发生在 32 位、优化关闭以及是否使用 -ansi
之间。这里,gcc
为 older/traditional 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 [=10=]xfffffff0,%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 [=10=]x14,%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 [=10=]xc,%esp
3e: 68 00 00 00 00 push [=10=]x0
43: e8 fc ff ff ff call 44 <main+0x44>
48: 83 c4 10 add [=10=]x10,%esp
4b: eb 10 jmp 5d <L01>
4d:L00 83 ec 0c sub [=10=]xc,%esp
50: 68 06 00 00 00 push [=10=]x6
55: e8 fc ff ff ff call 56 <main+0x56>
5a: 83 c4 10 add [=10=]x10,%esp
5d:L01 b8 00 00 00 00 mov [=10=]x0,%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 [=11=]x14,%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
。
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位的差值2335/1000 的 FP 表示和 2334/1000 的 80 位 FP 表示。这个数字恰好不同于 1/1000 的 80 位表示。这就是为什么你的测试程序的第二个版本的行为与没有 -ansi
时一样。在测试程序的第一个版本中,对 double
变量的赋值会导致数字四舍五入为双精度(64 位)浮点值。两者四舍五入后相等
考虑这两个程序。第一个在 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;
}
第二个打印 "Unequal" 有或没有 -ansi 选项。
int main () {
if (2.335 - 2.334 == 0.001) {
printf ("Equal\n");
} else {
printf ("Unequal\n");
}
return 0;
}
差异的根源是什么? 当然,实数不应该进行相等性测试是常识。我了解 IEEE754 标准对涉及浮点计算的(不)精度的影响。然而,据我所知,这两个程序在语义上应该是等价的,并且给出相同的结果。
在 C99 中删除的第一个 C89 模式中是否进行了一些隐式转换?
警告:这可能不是关于原因的完整答案,但这里有一些基于使用各种选项编译和反汇编输出的数据...
第二个程序[仅使用文字常量]不生成任何浮点指令。它执行单个 printf
。 (即所有计算都在编译器内完成)。
所以,以下仅限于第一个节目。
您没有指定给 gcc
的 [其他] 命令行选项,但我想只有 -ansi
。
我有一台 64 位 linux 机器 [gcc
5.3.1],所以我必须添加 -m32
.
没有它,反汇编 [for 64 bit],有或没有 -ansi
都是 same 并产生 Unequal
[它使用 XMM
说明]。使用 -O2
, 不会生成 浮点指令,只是一个 printf
[有或没有 -ansi
]。还是一样。
如果我使用-m32
和-O2
,只生成printf
,输出是Unequal
,不管-ansi
与否
唯一的差异发生在 32 位、优化关闭以及是否使用 -ansi
之间。这里,gcc
为 older/traditional 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 [=10=]xfffffff0,%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 [=10=]x14,%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 [=10=]xc,%esp
3e: 68 00 00 00 00 push [=10=]x0
43: e8 fc ff ff ff call 44 <main+0x44>
48: 83 c4 10 add [=10=]x10,%esp
4b: eb 10 jmp 5d <L01>
4d:L00 83 ec 0c sub [=10=]xc,%esp
50: 68 06 00 00 00 push [=10=]x6
55: e8 fc ff ff ff call 56 <main+0x56>
5a: 83 c4 10 add [=10=]x10,%esp
5d:L01 b8 00 00 00 00 mov [=10=]x0,%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 [=11=]x14,%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
。
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位的差值2335/1000 的 FP 表示和 2334/1000 的 80 位 FP 表示。这个数字恰好不同于 1/1000 的 80 位表示。这就是为什么你的测试程序的第二个版本的行为与没有 -ansi
时一样。在测试程序的第一个版本中,对 double
变量的赋值会导致数字四舍五入为双精度(64 位)浮点值。两者四舍五入后相等