开发者

How can I test for negative zero?

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


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.


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);
}


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.


x == 0 && 1 / x < 0


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


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜