views:

120

answers:

5

We have some code that looks like this:

inline int calc_something(double x) {
  if (x > 0.0) {
    // do something
    return 1;
  } else {
    // do something else
    return 0;
  }
}

Unfortunately, when using the flag /fp:fast, we get calc_something(0)==1 so we are clearly taking the wrong code path. This only happens when we use the method at multiple points in our code with different parameters, so I think there is some fishy optimization going on here from the compiler (Microsoft Visual Studio 2008, SP1).

Also, the above problem goes away when we change the interface to

inline int calc_something(const double& x) {

But I have no idea why this fixes the strange behaviour. Can anyone explane this behaviour? If I cannot understand what's going on we will have to remove the /fp:fastswitch, but this would make our application quite a bit slower.

+1  A: 

I think the behavior is correct.

You never compare a floating point number up to less than the holding type's precision.

Something that comes from zero may be equal, greater or less than another zero.

See http://floating-point-gui.de/

Pavel Radzivilovsky
+2  A: 

Hi,

what are the results of calc_something(0L), or calc_something(0.0f) ? It could be linked to the size of the types before casting. An integer is 4 bytes, a double is 8.

Have you tried looking at the asembled code, to see how the aforementioned conversion is done ?

Googling for 'fp fast', I found this post [social.msdn.microsoft.com]

LeGEC
same bad result for calc(0L), calc(0), calc(0.0), calc(0.0f)
martinus
+3  A: 

I'm not familiar enough with FPUs to comment with any certainty, but my guess would be that the compiler is letting an existing value that it thinks should be equal to x sit in on that comparison. Maybe you go y = x + 20.; y = y - 20; y is already on the FP stack, so rather than load x the compiler just compares against y. But due to rounding errors, y isn't quite 0.0 like it is supposed to be, and you get the odd results you see.

For a better explanation: Why is cos(x) != cos(y) even though x == y? from the C++FAQ lite. This is part of what I'm trying to get across, I just couldn't remember where exactly I had read it until just now.

Changing to a const reference fixes this because the compiler is worried about aliasing. It forces a load from x because it can't assume its value hasn't changed at some point after creating y, and since x is actually exactly 0.0 [which is representable in every floating point format I'm familiar with] the rounding errors vanish.

I'm pretty sure MS provides a pragma that allows you to set the FP flags on a per-function basis. Or you could move this routine to a separate file and give that file custom flags. Either way, it could prevent your whole program from suffering just to keep that one routine happy.

Dennis Zickefoose
Yep, try `#pragma float_control(...)` for Visual Studio: http://msdn.microsoft.com/en-us/library/45ec64h6.aspx - you could disable fp:fast around just the function in question.
AshleysBrain
+1  A: 

As I've said in other question, compilers suck at generating floating point code. The article Dennis links to explains the problems well. Here's another: An MSDN article.

If the performance of the code is important, you can easily1 out-perform the compiler by writing your own assembler code. If your algoritm is vectorisable then you can make use of SIMD too (with a slight loss of precision though).

  1. Assuming you understand the way the FPU works.
Skizz
Your MSDN article got gomped somewhere.
Dennis Zickefoose
Updated the link.
Skizz
+2  A: 

inline int calc_something(double x) will (probably) use an 80 bits register. inline int calc_something(const double& x) would store the double in memory, where it takes 64 bits. That at least explains the difference between the two.

However, I find your test quite fishy to begin with. The results of calc_something are extremely sensitive to rounding of its input. Your FP algorithms should be robust to rounding. calc_something(1.0-(1.0/3.0)*3) should be the same as calc_something(0.0).

MSalters