2011-01-19 88 views
36

最初我以为Math.Sign将是正确的路,但经过一次测试后,它似乎将-0.0+0.0视为相同。如何测试负数零?

+7

什么是*负零*,它与*正零*有什么不同?它与零* *有什么不同?在数学上它们是相同的。 * IEEE 754ically *可能不是。 – 2011-01-19 19:37:57

+0

按位,-0.0的表示与+0.0有什么不同? 0x800000和0x000000? – Davidann 2011-01-19 19:39:31

+3

@Darin Dimitrov,你有没有参加过数学分析课程? :P – 2011-01-19 19:39:42

回答

42

这里做的难看破解方法:

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

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

基本上就为-0.0确切位模式测试,但不必硬编码。

14

经过一番搜索之后,我终于把它变成了C#规范的Section 7.7.2,并提出了这个解决方案。

private static bool IsNegativeZero(double x) 
{ 
    return x == 0.0 && double.IsNegativeInfinity(1.0/x); 
} 
8

负零具有符号位设置。因此:

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

编辑:正如OP指出的,这在Release模式下不起作用。 x86 JIT优化器认真考虑if()语句并直接加载0,而不是加载。这确实是更高性能的。但是这会导致消极的零点消失。该代码需要去调整,以防止这种情况:

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

这是x86的抖动比较典型的行为顺便说一句,它不处理角落的情况非常好,当它优化浮点代码。在这方面,x64抖动要好得多。虽然可以说没有比赋予负数零的含义更糟糕的角落案例。预先警告。

7

这是另一个黑客。它利用了一个事实,即Equals上一个struct会做的,而不是呼吁其成员Equals按位比较:

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

8
x == 0 && 1/x < 0 
1

更一般地,你可以做,

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

或替代,如果你愿意,

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

后来给出了调试,但大约50%的性能改良效果。一旦在释放模式下编译并从命令行运行,则没有显着差异

我无法使用不安全的代码生成性能改进,但这可能是由于我缺乏经验。