Recently, I wrote a small program and compiled it using mingw32(on Windows8) of 2 different versions. Surprisingly, I got two different reusults. I tried disassmbling it but found nothing special. Could anyone help me? Thank you.
the exe files: https://www.dropbox.com/s/69sq1ttjgwv1qm3/asm.7z
results: 720720(gcc version 4.5.2), 720719(gcc version 4.7.0)
compiler flags: -lstdc++ -static
Code snipped as following:
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
int a = 55440, b = 13;
a *= pow(b, 1);
cout << a << endl;
return 0;
}
Assembly output(4.5.2):
http://pastebin.com/EJAkVAaH
Assembly output(4.7.0):
http://pastebin.com/kzbbFGs6
I've been able to reproduce the problem with a single version of the compiler.
Mine is MinGW g++ 4.6.2.
When I compile the program as g++ -g -O2 bugflt.cpp -o bugflt.exe
, I get 720720
.
This is the disassembly of main()
:
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
call ___main
movl $720720, 4(%esp)
movl $__ZSt4cout, (%esp)
call __ZNSolsEi
movl %eax, (%esp)
call __ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
xorl %eax, %eax
leave
ret
As you can see, the value is calculated at compile time.
When I compile it as g++ -g -O2 -fno-inline bugflt.cpp -o bugflt.exe
, I get 720719
.
This is the disassembly of main()
:
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
call ___main
movl $1, 4(%esp)
movl $13, (%esp)
call __ZSt3powIiiEN9__gnu_cxx11__promote_2INS0_11__enable_ifIXaasrSt15__is_arithmeticIT_E7__valuesrS3_IT0_E7__valueES4_E6__typeES6_E6__typeES4_S6_
fmuls LC1
fnstcw 30(%esp)
movw 30(%esp), %ax
movb $12, %ah
movw %ax, 28(%esp)
fldcw 28(%esp)
fistpl 4(%esp)
fldcw 30(%esp)
movl $__ZSt4cout, (%esp)
call __ZNSolsEi
movl $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
movl %eax, (%esp)
call __ZNSolsEPFRSoS_E
xorl %eax, %eax
leave
ret
...
LC1:
.long 1196986368 // 55440.0 exactly
If I replace the call to exp()
with loading 13.0 like this:
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
call ___main
movl $1, 4(%esp)
movl $13, (%esp)
// call __ZSt3powIiiEN9__gnu_cxx11__promote_2INS0_11__enable_ifIXaasrSt15__is_arithmeticIT_E7__valuesrS3_IT0_E7__valueES4_E6__typeES6_E6__typeES4_S6_
fildl (%esp)
fmuls LC1
fnstcw 30(%esp)
movw 30(%esp), %ax
movb $12, %ah
movw %ax, 28(%esp)
fldcw 28(%esp)
fistpl 4(%esp)
fldcw 30(%esp)
movl $__ZSt4cout, (%esp)
call __ZNSolsEi
movl $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
movl %eax, (%esp)
call __ZNSolsEPFRSoS_E
xorl %eax, %eax
leave
ret
I get 720720
.
If I set the same rounding and precision control fields of the x87 FPU control word for the duration of exp()
as for the fistpl 4(%esp)
instruction like this:
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
call ___main
movl $1, 4(%esp)
movl $13, (%esp)
fnstcw 30(%esp)
movw 30(%esp), %ax
movb $12, %ah
movw %ax, 28(%esp)
fldcw 28(%esp)
call __ZSt3powIiiEN9__gnu_cxx11__promote_2INS0_11__enable_ifIXaasrSt15__is_arithmeticIT_E7__valuesrS3_IT0_E7__valueES4_E6__typeES6_E6__typeES4_S6_
fldcw 30(%esp)
fmuls LC1
fnstcw 30(%esp)
movw 30(%esp), %ax
movb $12, %ah
movw %ax, 28(%esp)
fldcw 28(%esp)
fistpl 4(%esp)
fldcw 30(%esp)
movl $__ZSt4cout, (%esp)
call __ZNSolsEi
movl $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
movl %eax, (%esp)
call __ZNSolsEPFRSoS_E
xorl %eax, %eax
leave
ret
I get 720720
as well.
From this I can only conclude that exp()
isn't calculating 131 precisely as 13.0.
It may be worth looking at the source code of that __gnu_cxx::__promote_2<__gnu_cxx::__enable_if<(std::__is_arithmetic<int>::__value)&&(std::__is_arithmetic<int>::__value), int>::__type, int>::__type std::pow<int, int>(int, int)
to see exactly how it manages to screw up exponentiation with integers (see, unlike C's exp()
it takes two ints
instead of two doubles
).
But I wouldn't blame exp()
for that. C++11 defines float pow(float, float)
and long double pow(long double, long double)
in addition to C's double pow(double, double)
. But there's no double pow(int, int)
in the standard.
The fact that the compiler provides a version for integer arguments does not make any additional guarantee about the precision of the result. If exp()
calculates ab as
ab = 2b * log2(a)
or as
ab = eb * ln(a)
for floating-point values, there definitely can be rounding errors in the process.
If the "integer" version of exp()
does something similar and incurs a similar loss of precision due to rounding errors, it still does its job right. And it does it even if the loss of precision is due to some silly bug and not because of the normal rounding errors.
However surprising this behavior may seem, it's correct. Or so I believe until proven wrong.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With