10

I have some code that has been port from Java to C++

// since this point is a vector from (0,0,0), we can just take the
// dot product and compare
double r = point.dot(normal);
return (r>=0.0);

But in C++ r can be either +0.0 or -0.0, when r equals -0.0 it fails the check.

I've tried to adjust for negative zero in the code below but it never hits the DEBUG("Negative zero") line. But r2 does print out equal to +0.0.

// since this point is a vector from (0,0,0), we can just take the
// dot product and compare
double r = point.dot(normal);
if (std::signbit(r)){
    double r2 = r*-1;
    DEBUG("r=%f r=%f", r,r2);
    if (r2==0.0) {
        DEBUG("Negative zero");
        r = 0.0; //Handle negative zero
    }
}
return (r>=0.0);

Any suggestion?

TESTING CODE:

DEBUG("point=%s", point.toString().c_str());
DEBUG("normal=%s", normal->toString().c_str());
double r = point.dot(normal);
DEBUG("r=%f", r);
bool b = (r>=0.0);
DEBUG("b=%u", b);

TESTING RESULTS:

DEBUG - point=Vector3D[ x=1,y=0,z=0 ]
DEBUG - normal=Vector3D[ x=0,y=-0.0348995,z=0.0348782 ]
DEBUG - r=0.000000
DEBUG - b=1
DEBUG - point=Vector3D[ x=1,y=0,z=0 ]
DEBUG - normal=Vector3D[ x=-2.78269e-07,y=0.0174577,z=-0.0174391 ]
DEBUG - r=-0.000000
DEBUG - b=0

GCC:

Target: x86_64-linux-gnu
--enable-languages=c,c++,fortran,objc,obj-c++ 
--prefix=/usr 
--program-suffix=-4.6 
--enable-shared 
--enable-linker-build-id 
--with-system-zlib 
--libexecdir=/usr/lib 
--without-included-gettext 
--enable-threads=posix 
--with-gxx-include-dir=/usr/include/c++/4.6 
--libdir=/usr/lib 
--enable-nls 
--with-sysroot=/ 
--enable-clocale=gnu 
--enable-libstdcxx-debug 
--enable-libstdcxx-time=yes 
--enable-gnu-unique-object 
--enable-plugin 
--enable-objc-gc 
--disable-werror 
--with-arch-32=i686 
--with-tune=generic 
--enable-checking=release 
--build=x86_64-linux-gnu 
--host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) 

FLAGS:

CXXFLAGS += -g -Wall -fPIC

ANSWER:

I've used @amit's answer to do the following.

return (r>=(0.0-std::numeric_limits<double>::epsilon()));

Which seems to work.

Justin
  • 4,196
  • 4
  • 24
  • 48
  • the behavior is non-standard. what compiler and floating point math options are you using. i suspect g++ with fastmath – Cheers and hth. - Alf Dec 07 '12 at 17:29
  • also, please post a complete and not-too-large example that exhibits the problem, with complete build instructions so that it is *reproducable* – Cheers and hth. - Alf Dec 07 '12 at 17:32
  • 1
    thanks. as it turns out (see my answer) it was only the described behavior, about "negative zero" comparing unequal to zero, that was non-standard. in reality there is apparently no negative zero, just a very small negative value, which with the given output format is *presented* as all zero digits with a minus sign in front. – Cheers and hth. - Alf Dec 07 '12 at 18:01
  • @Cheersandhth.-Alf and hth Yea, that was the problem. I've used std::numeric_limits::epsilon() to help adjust for that problem. – Justin Dec 07 '12 at 18:07
  • 1
    oh I forgot to mention: since the "problem" was only in the *perception* of what went on, it is by far most likely the the best course of action would be to *do nothing* (except possibly about the presentation). by comparing with epsilon and nulling, you have possibly introduced a bug. which, ironically, might *look* like correct behavior when evaluated in the context of the erroneous perception. – Cheers and hth. - Alf Dec 07 '12 at 18:11
  • The accepted answer and your `r>(0.0-std::numeric_limits::epsilon())` does nothing to differentiate between `-0` and `0`; both are less than epsilon. The values that underflow to `-0` (if flow-to-zero/denormals-are-zero flags are set) lie between `0` and epsilon. – RJFalconer Jun 04 '14 at 11:55
  • If you want to detect positive or zero r values, then you should not test -0.0 to true, as it's a negative number. It's only too small to be represented with a non zero mantissa but still it's negative. – galinette Oct 24 '21 at 20:51

7 Answers7

11

On some older systems (i.e. pre-IEE754) you may find equality checks against 0 fail for negative-0:

if (a == 0.0) // when a==-0.0, fails

you can work around this by adding 0.0 to a value before the comparison:

if ((a+0.0) == 0.0) // when a == -0.0, succeeds

I would caution, however, that combinations of hardware/software that really require this are quite unusual. The last time I had to do it was on a Control Data mainframe. Even there, it only arose under somewhat unusual circumstances: the Fortran compiler allowed negative zeros to be generated, and knew to compensate for them in comparisons. The Pascal compiler generated code to turn negative zeros into normal zeros as part of a computation.

Therefore, if you wrote a routine in Fortran and called it from Pascal, you could run into this problem, and prevent it as above by adding 0.0 before doing a comparison.

I'm gonna put pretty good odds that your problem doesn't really stem from comparisons with negative zero though. All reasonably modern hardware of which I'm aware handles this entirely automatically, so software never has to consider it at all.

RJFalconer
  • 10,890
  • 5
  • 51
  • 66
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • 1
    Considering the OP's new more detailed example, I think you won that bet :-) – Cheers and hth. - Alf Dec 07 '12 at 18:07
  • 1
    `if (a == 0.0) // when a==-0.0, fails` where? This violates IEEE 754. – RJFalconer Jun 04 '14 at 12:02
  • @RJFalconer: As stated in the answer, the specific example in question was a Control Data mainframe (which predated IEEE 754 by several years). As my closing sentence said, this is handled automatically on "all reasonably modern hardware". Next time you might want to actually read the answer before down-voting. – Jerry Coffin Jun 04 '14 at 16:01
  • Actually that's not quite what you state in your answer. You merely say that's the last time you had to do it. If you clarify your opening I'll remove the down vote. – RJFalconer Jun 04 '14 at 16:33
  • @RJFalconer: I'm confused about what you think needs clarification. As to what I said, here's a CnP: " All reasonably modern hardware of which I'm aware handles this entirely automatically, so software never has to consider it at all." – Jerry Coffin Jun 04 '14 at 16:36
  • To save comment spam I'll make an edit; please revert/update as you see fit. – RJFalconer Jun 04 '14 at 16:38
  • Does adding 0.0 make the calculation notably slower? I've ran into a situation where -0 is returned when it shouldn't be. – Aaron Franke May 31 '18 at 11:07
10

Well, a generic suggestion when using doubles is remembering they are not exact. Thus, if equality is important - using some tolerance factor is usually advised.

In your case:

if (|r - 0.0| >= EPSILON)

where EPSILON is your tolerance factor, will yield true if r is not 0.0, with at least EPSILON interval.

amit
  • 175,853
  • 27
  • 231
  • 333
  • Thanks. But I was hoping for a more general solution. This may be the best approach, I'll have to keep looking. – Justin Dec 07 '12 at 17:25
  • Change my question to include an answer – Justin Dec 07 '12 at 18:07
  • 1
    Although correct and notable, this does not answer the question as asked as it does not distinguish between `0.0` and `-0.0`; they're equal to each other and both would be less than epsilon. – RJFalconer Jun 04 '14 at 11:59
  • @RJFalconer Thank you, I somehow missed an absolute value, that should be there. (fixed). Also note, that for `r==0.0` and `r== -0.0`, `r < 0.0 - EPSILON` (assuming positive EPSILON, of course). – amit Jun 04 '14 at 13:07
  • I don't think that's right. For `r==-0.0`, `r > 0.0 - EPSILON`. Even the smallest epsilon (`std::numeric_limits::epsilon`) is less than `-0.0`. – RJFalconer Jun 04 '14 at 14:07
  • @RJFalconer I was giving the "find not equal", note that `(-EPSILON < 0.0) == (-EPSILON < -0.0)`, and that's the point - it doesn't matter which zero it is, you can work with both of them the same way using this approach. (Finding the equal is just doing `not` on the above, and fairly simple). – amit Jun 04 '14 at 14:10
  • `if (r >= |0.0 - EPSILON|)` This is unnecessary; `if (r >= EPSILON)` suffices. – Don Larynx May 01 '15 at 20:45
7

The OP mentions std::signbit in his question, but it should be in an answer to be found quickly, so here it is:

This works since C++11 to distinguish between -0.0 and +0.0:

#include <cmath>
std::signbit(x) // true iif sign bit of x is set i.e. x is negative

std::signbit on cppreference

Gabriel Devillers
  • 3,155
  • 2
  • 30
  • 53
3

Presumably you meant something like if (r2==-0.0). Nonetheless, both negative 0 and positive 0 will compare equal. For all intents and purposes there's no difference between the two. You probably don't need to have a special case for negative 0. Your comparison r >= 0 should be true for either negative or positive 0.

Joseph Mansfield
  • 108,238
  • 20
  • 242
  • 324
1

Consider:

#include <stdio.h>
#include <iostream>
using namespace std;

int main()
{
    double const x = -2.78269e-07;

    printf( "printf: x=%f\n", x );
    cout << "cout: x=" << x << endl;
}

With result (using Visual C++ 11.0):

[D:\dev\test]
> cl foo.cpp
foo.cpp

[D:\dev\test]
> foo
printf: x=-0.000000
cout: x=-2.78269e-007

[D:\dev\test]
> _

This seems awfully similar to the mysterious result in the question.

It's my considered opinion that it quacks like the question's result, looks like the question's result and waddles like the question's result.

So, I believe that the not shown computation code produced the value 2.78269e-007.


So, in conclusion, it was apparently only the described behavior, about “negative zero” comparing unequal to zero, that was non-standard. In reality there is apparently no negative zero, just a very small negative value. Which with the given output format is presented as all zero digits with a minus sign in front.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
0

It's just a very small negative number that the console couldn't print it for you
You can try checking for it depending on your precision so you can replace it with a pure zero.

std::cout << std::setprecision(7) << (abs(value) < 0.0000005f ? 0 : value);

Notice how I added 7 places to the floating precision as I specified with std::setprecision().

It depends on how accurately you want to print your floats/double.

Beyondo
  • 2,952
  • 1
  • 17
  • 42
0

These will do the trick - but the compiler might optimize it out:

if (-v == 0.0) v = 0.0;

if (v == -0.0) v = 0.0;

So I thinks this is better:

if (v < DBL_EPSILON && v > -DBL_EPSILON)
    v = 0.0;
  • 1
    Note that there are many numbers that have absolute values less than `DBL_EPSILON` that are quite definitely *not* zero. That constant is the smallest value that can be added to `1` such that the result is not (still) 1. – Adrian Mole Aug 02 '22 at 11:49
  • should be `if (std::nextafter(0., -1.) < v && v < std::nextafter(0., 1.))` – mheyman Apr 13 '23 at 15:10