views:

692

answers:

5

In my numerical simulation I have code similar to the following snippet

double x;
do {
  x = /* some computation */;
} while (x <= 0.0);
/* some algorithm that requires x to be (precisely) larger than 0 */

With certain compilers (e.g. gcc) on certain platforms (e.g. linux, x87 math) it is possible that x is computed in higher than double precision ("with excess precision"). (Update: When I talk of precision here, I mean precision /and/ range.) Under these circumstances it is conceivable that the comparison (x <= 0) returns false even though the next time x is rounded down to double precision it becomes 0. (And there's no guarantee that x isn't rounded down at an arbitrary point in time.)

Is there any way to perform this comparison that

  • is portable,
  • works in code that gets inlined,
  • has no performance impact and
  • doesn't exclude some arbitrary range (0, eps)?

I tried to use (x < std::numeric_limits<double>::denorm_min()) but that seemed to significantly slow down the loop when working with SSE2 math. (I know that denormals can slow down a computation, but I didn't expect them to be slower to just move around and compare.)

Update: An alternative is to use volatile to force x into memory before the comparison, e.g. by writing

} while (*((volatile double*)&x) <= 0.0);

However, depending on the application and the optimizations applied by the compiler, this solution can introduce a noticeable overhead too.

Update: The problem with any tolerance is that it's quite arbitrary, i.e. it depends on the specific application or context. I'd prefer to just do the comparison without excess precision, so that I don't have to make any additional assumptions or introduce some arbitrary epsilons into the documentation of my library functions.

+1  A: 

I wonder whether you have the right stopping criterion. It sounds like x <= 0 is an exception condition, but not a terminating condition and that the terminating condition is easier to satisfy. Maybe there should be a break statement inside your while loop that stops the iteration when some tolerance is met. For example, a lot of algorithm terminate when two successive iterations are sufficiently close to each other.

John D. Cook
No, in this case it really is a terminating condition (or the logical negation of the terminating condition). I could of course check at a later point whether some division-by-zero occurred, but in this case (and many similar) I really need my function not to return any value less than or equal to 0
Stephan
A: 

Well, GCC has a flag, -fexcess-precision which causes the problem you are discussing. It also has a flag, -ffloat-store , which solves the problem you are discussing.

"Do not store floating point variables in registers. This pre-vents undesirable excess precision on machines such as the 68000 where the floating registers (of the 68881) keep more precision than a double is supposed to have."

I doubt that solution has no performance impact, but the impact is probably not overly expensive. Random googling suggests it costs about 20%. Actually, I don't think there is a solution which is both portable and has no performance impact, since forcing a chip to not use excess precision is often going to involve some non-free operation. However, this is probably the solution you want.

Brian
On x86-based platforms with SSE2 support, using "-mfpmath=sse" should solve the problem without any negative performance impact. However, prescribing non-standard compiler options isn't really portable in my book.
Stephan
A: 

Be sure to make that check an absolute value. It needs to be an epsilon around zero, above and below.

duffymo
I don't understand, `(x < eps)` as the do while condition would also reject negative values, right?
Stephan
The problem is not the usual floating-point accuracy issue, it's that the original poster is getting "(x >= 0)" to return false, in a loop condition, when "x == 0" is true outside the loop.
David Thornley
+1  A: 

As Arkadiy stated in the comments, an explicit cast ((double)x) <= 0.0 should work - at least according to the standard.

C99:TC3, 5.2.4.2.2 §8:

Except for assignment and cast (which remove all extra range and precision), the values of operations with floating operands and values subject to the usual arithmetic conversions and of floating constants are evaluated to a format whose range and precision may be greater than required by the type. [...]


If you're using GCC on x86, you can use the flags -mpc32, -mpc64 and -mpc80 to set the precision of floating-point operations to single, double and extended double precision.

Christoph
But relying on C99 isn't really portable. If at all, than only very recent GCC versions will honor this guarantee, not to mention all the other compilers...
Stephan
It's all a question about how portable your code should be. I'd go with the standard and just test the compilers most likely to be used. If they're not compliant, I'd add compiler-specific flags to the makefile...
Christoph
PS: "If at all, than only very recent GCC versions will honor this guarantee, not to mention all the other compilers" - did you test this, or was that just an assumption based on the incomplete C99 support in most (or even all) current compilers?
Christoph
It seems only GCC 4.5 will support C99-conforming excess precision, see http://gcc.gnu.org/ml/gcc-patches/2008-11/msg00105.html
Stephan
Interesting, but sad. C99 is ten years old now, and still no decent compiler support. Hopefully, clang and the revival of PCC will spark some interest in standard compliance...
Christoph
+1  A: 

In your question, you stated that using volatile will work but that there'll be a huge performance hit. What about using the volatile variable only during the comparison, allowing x to be held in a register?

double x; /* might have excess precision */
volatile double x_dbl; /* guaranteed to be double precision */
do {
  x = /* some computation */;
  x_dbl = x;
} while (x_dbl <= 0.0);


You should also check if you can speed up the comparison with the smallest subnormal value by using long double explicitly and cache this value, ie

const long double dbl_denorm_min = static_cast<long double>(std::numeric_limits<double>::denorm_min());

and then compare

x < dbl_denorm_min

I'd assume that a decent compiler would do this automatically, but one never knows...

Christoph
It depends a bit on the compiler and the specific loop. It seems to be faster than comparing with a denormal value, but it still introduces a noticeable overhead on platforms without excess precision.
Stephan
Nevertheless, I'll probably go with some macro/preprocessor definition that expands to `while (*((volatile double*)` on platforms with potential excess precision.
Stephan
The denormal comparisons only slow down SSE2 math, not x87 math where proper long doubles are available.
Stephan
@Stephan: I see. Then, I'm out of ideas - I don't think there's anything more you can do aside from compiler-specific flags or dropping to assembly level...
Christoph