visual c++ - Different values depending on floating point exception flags set -
short question:
how can setting _em_invalid exception flag on fpu result in different values?
long question:
in our project have turned off floating point exceptions in our release build, turned on zerodivide, invalid , overflow using _controlfp_s() in our debug build. in order catch errors if there.
however, results of numerical calculations (involving optimisation algorithms, matrix inversion, monte carlo , sorts of things) consistent between debug , release build make debugging easier.
i expect setting of exception flags on fpu should not affect calculated values - whether exceptions thrown or not. after working backwards through our calculations can isolate below code example shows there difference on last bit when calling log() function.
this propagates 0.5% difference in resulting value.
the below code give shown program output when adding new solution in visual studio 2005, windows xp , compile in debug configuration. (release give different output, that's because optimiser reuses result first call log().)
i hope can shed bit of light on this. thanks.
/* program output: xi, 3893f76f, 7.4555176582633598 k, c0a682c7, 7.44466687218 untouched x, da8caea1, 0.0014564635732296288 invalid exception on x, da8caea2, 0.001456463573229629 invalid exception off x, da8caea1, 0.0014564635732296288 */ #include <float.h> #include <math.h> #include <limits> #include <iostream> #include <iomanip> using namespace std; int main() { unsigned umaskold = 0; errno_t err; cout << std::setprecision (numeric_limits<double>::digits10 + 2); double xi = 7.4555176582633598; double k = 7.44466687218; double x; cout << "xi, " << hex << setw(8) << setfill('0') << *(unsigned*)(&xi) << ", " << dec << xi << endl; cout << "k, " << hex << setw(8) << setfill('0') << *(unsigned*)(&k) << ", " << dec << k << endl; cout << endl; cout << "untouched" << endl; x = log(xi/k); cout << "x, " << hex << setw(8) << setfill('0') << *(unsigned*)(&x) << ", " << dec << x << endl; cout << endl; cout << "invalid exception on" << endl; ::_clearfp(); err = ::_controlfp_s(&umaskold, 0, _em_invalid); x = log(xi/k); cout << "x, " << hex << setw(8) << setfill('0') << *(unsigned*)(&x) << ", " << dec << x << endl; cout << endl; cout << "invalid exception off" << endl; ::_clearfp(); err = ::_controlfp_s(&umaskold, _em_invalid, _em_invalid); x = log(xi/k); cout << "x, " << hex << setw(8) << setfill('0') << *(unsigned*)(&x) << ", " << dec << x << endl; cout << endl; return 0; }
this not complete answer, long comment.
i suggest isolate code questionable calculations , put in subroutine, preferably in source module compiled separately. like:
void foo(void) { double xi = 7.4555176582633598; double k = 7.44466687218; double x; x = log(xi/k); …insert output statements here… }
then call routine different settings:
cout << "untouched:\n"; foo(); cout << "invalid exception on:\n"; …change fp state… foo();
this guarantees same instructions executed in each case, eliminating possibility compiler has reason generated separate code each sequence. way have compiled code, suspect possible compiler may have used 80-bit arithmetic in 1 case , 64-bit arithmetic in another, or may have used 80-bit arithmetic converted result 64-bit in 1 case not another
once done, can partition , isolate code further. e.g., try evaluating xi/k
once before of tests, storing in double
, , passing foo
parameter. tests whether log
call differs depending on floating-point state. suspect case, unlikely division operation differ.
another advantage of isolating code way step through in debugger see behavior diverges. step through it, 1 instruction @ time, different floating-point states simultaneously in 2 windows , examine results @ each step see divergence is. if there no divergence time reach log
call, should step through that, too.
incidental notes:
if know xi
, k
close each other, better compute log(xi/k)
log1p((xi-k)/k)
. when xi
, k
close each other, subtraction xi-k
exact (has no error), , quotient has more useful bits (the 1 knew , 0 bits following gone).
the fact slight changes in floating-point environment cause .5% change in result implies calculations sensitive error. suggests that, if make results reproducible, errors exist in floating-point arithmetic cause result inaccurate. is, final error still exist, not called attention difference between 2 different ways of calculating.
it appears in c++ implementation unsigned
4 bytes double
8 bytes. printing encoding double
aliasing unsigned
omits half of bits. instead, should convert pointer double
pointer const char
, print sizeof(double)
bytes.
Comments
Post a Comment