How can I test for negative zero?
Asked Answered
E

8

46

Initially I thought Math.Sign would be the proper way to go but after running a test it seems that it treats -0.0 and +0.0 the same.

Examinee answered 19/1, 2011 at 19:36 Comment(8)
What is negative zero and how is it different than positive zero and how is it different than zero? Mathematically their are the same. IEEE 754ically maybe not.Sinter
Bitwise, how does the representation of -0.0 differ from +0.0? 0x800000 and 0x000000?Geer
@Darin Dimitrov, have you ever attended a mathematical analysis course? :PBrat
@klez, yes I have. I even have a degree in mathematics :-)Sinter
Mathematically you may know from which side of zero you approached it, computationally you may not. Nice to know sometimes.Pirri
For a complete question, please define negative zero. For us doubters, you may have to prove that it actually exists. As soon as you prove it exists, you'll have code that tests for it.Pillory
@abelenky: en.wikipedia.org/wiki/Negative_zeroPettigrew
If you're talking about IEEE doubles, it is well defined.Fermanagh
H
51

Here's a grotty hack way of doing it:

private static readonly long NegativeZeroBits =
    BitConverter.DoubleToInt64Bits(-0.0);

public static bool IsNegativeZero(double x)
{
    return BitConverter.DoubleToInt64Bits(x) == NegativeZeroBits;
}

Basically that's testing for the exact bit pattern of -0.0, but without having to hardcode it.

Heptastich answered 19/1, 2011 at 19:42 Comment(7)
It seems your solution is about 25 times faster than mine.Examinee
As an added bonus, no additional overhead :) You have to love the power of unsafe code sometimes :)Renferd
@Renferd - Now this is awesome: return *(((long*) &value));Examinee
It's not really grotty, because (a) it is well encapsulated and (b) if the user can't rely on IEEE floating point, then she can't rely on -0.0 even existing. A better abstraction would be bool IsNegative(double x){return BitConverter.DoubleToInt64Bits(x) < 0;}. That's a function which should actually be in the standard Math class.Alfonzoalford
I saw this referred to in another answer and wondered why it's not in MiscUtil. But if you say its grotty...Forefinger
@StephenKennedy: It's mostly not there because I've never had a reason to need it myself.Heptastich
Just wanted to add a friendly remainder that this works for binary floating point (which is the case here), but not for decimal floating point (which allows for multiple representations of +/-0).Wharf
E
16

After a bit of searching I finally made it to Section 7.7.2 of the C# specification and came up with this solution.

private static bool IsNegativeZero(double x)
{
    return x == 0.0 && double.IsNegativeInfinity(1.0 / x);
}
Examinee answered 19/1, 2011 at 19:43 Comment(5)
While I applaud the ingenuity, I'm not sure it's the most readable approach :)Heptastich
If you change the 1.0 to double.Epsilon, I think it may be okay though :)Heptastich
@Jon - I added a check for zero.Examinee
Curious that 1 / double.Epsilon would evaluate to infinity.Ruthieruthless
@Anthony Pegram, not really. double.Epsilon is denormalized, so it is smaller than 2**(min exponent). There is no equivalent of denormalization for extremely large values, so the reciprocal is too large to be representable.Fermanagh
S
9

Negative zero has the sign bit set. Thus:

    public static bool IsNegativeZero(double value) {
        if (value != 0) return false;
        int index = BitConverter.IsLittleEndian ? 7 : 0;
        return BitConverter.GetBytes(value)[index] == 0x80;
    }

Edit: as the OP pointed out, this doesn't work in Release mode. The x86 JIT optimizer takes the if() statement seriously and loads zero directly rather than loading value. Which is indeed more performant. But that causes the negative zero to be lost. The code needs to be de-tuned to prevent this:

    public static bool IsNegativeZero(double value) {
        int index = BitConverter.IsLittleEndian ? 7 : 0;
        if (BitConverter.GetBytes(value)[index] != 0x80) return false;
        return value == 0;
    }

This is quite typical behavior for the x86 jitter btw, it doesn't handle corner cases really well when it optimizes floating point code. The x64 jitter is much better in that respect. Although there's arguably no worse corner case than giving meaning to negative zero. Be forewarned.

Superpatriot answered 19/1, 2011 at 20:8 Comment(4)
For some reason, when I build this with a target of x86 with optimizations enabled and run it outside of Visual Studio passing -0.0 will return false. Oh and this seems to be the middle ground performance wise.Examinee
I repro, the if statement confuzzles the JIT optimizer. It now assumes the value equals zero, not negative zero and loads it directly with fldz rather than loading value. Because that's quicker. You are going to fight this badly in your own code as well. Post updated.Superpatriot
Unfortunately to fully implement the ECMAScript standard I must be aware of -0.0.Examinee
I'm seeing standards collide head-on. Good luck with it.Superpatriot
A
8

Here's another hack. It takes advantage of the fact that Equals on a struct will do a bitwise comparison instead of calling Equals on its members:

struct Negative0
{
    double val;
    public static bool Equals(double d)
    {
        return new Negative0 { val = -0d }.Equals(new Negative0 { val = d });
    }
}

Negative0.Equals(0); // false
Negative0.Equals(-0.0); // true

Ascribe answered 19/1, 2011 at 20:43 Comment(1)
ValueType.Equals not always performs a bitwise comparison. And, unfortunately, exact conditions when it chooses to do so are undocumented, and may vary between CLR versions/platforms.Ducks
D
8
x == 0 && 1 / x < 0
Ducks answered 1/6, 2012 at 1:20 Comment(1)
Integers do not have distinct positive zero and negative zero.Ducks
I
3

More generally, you can do,

bool IsNegative(double value)
{
    const ulong SignBit = 0x8000000000000000;
    return ((ulong)BitConverter.DoubleToInt64Bits(value) & SignBit) == SignBit;
}

or alternatively, if you prefer,

[StructLayout(LayoutKind.Explicit)]
private struct DoubleULong
{
    [FieldOffset(0)]
    public double Double;

    [FieldOffset(0)]
    public readonly ulong ULong;
}

bool IsNegative(double value)
{
    var du = new DoubleULong { Double = value };
    return ((du.ULong >> 62) & 2) == 2;
}

The later gives an approximate 50% performance improvment in debug but. Once compiled in release mode and run from the command line there is no significant difference.

I couldn't generate a performance improvement using unsafe code either but this may be due to my inexperience.

Inanition answered 3/11, 2014 at 14:42 Comment(0)
P
1

I'm a bit baffled that after sitting here for more than 13 years (at the time of writing), this question has all these excellent (and somewhat elaborate) answers but not the most "straightforward" (IMO) one:

static bool IsNegativeZero(double d) => Double.IsNegative(d) && d == 0.0;

Note that, according to the IEEE spec, -0.0 compares equal to +0.0, so there's no need to write d == -0.0 although that will also work.

Pacific answered 12/3, 2024 at 21:36 Comment(1)
double.IsNegative() was first introduced in .NET Core 2.1 (released in 2018), so the function didn't exist at the time the earlier answers were written.Lambdacism
R
1

Since .NET 7.0:

if (value == double.NegativeZero)
Reform answered 15/6, 2024 at 15:8 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.