C# 如何测试负零?

C# 如何测试负零?,c#,.net,floating-point,C#,.net,Floating Point,起初我认为数学。符号是正确的方法,但在运行测试后,它似乎将-0.0和+0.0视为相同的方法。下面是一个拙劣的方法: private static readonly long NegativeZeroBits = BitConverter.DoubleToInt64Bits(-0.0); public static bool IsNegativeZero(double x) { return BitConverter.DoubleToInt64Bits(x) == Negativ

起初我认为数学。符号是正确的方法,但在运行测试后,它似乎将
-0.0
+0.0
视为相同的方法。

下面是一个拙劣的方法:

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

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

基本上,这是在测试-0.0的精确位模式,但不需要硬编码。

经过一番搜索,我终于找到了C#规范的一部分,并提出了这个解决方案

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

负零设置了符号位。因此:

    public static bool IsNegativeZero(double value) {
        if (value != 0) return false;
        int index = BitConverter.IsLittleEndian ? 7 : 0;
        return BitConverter.GetBytes(value)[index] == 0x80;
    }
编辑:正如OP所指出的,这在发布模式下不起作用。x86 JIT优化器认真对待if()语句,直接加载零而不是加载值。这确实更有效。但这会导致负零丢失。需要对代码进行去调优以防止出现这种情况:

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

这是x86 jitter的典型行为,顺便说一句,它在优化浮点代码时不能很好地处理死角情况。x64抖动在这方面要好得多。尽管没有比赋予负零意义更糟糕的情况了。预先警告。

这是另一个黑客攻击。它利用了这样一个事实,即
结构上的
Equals
将执行位比较,而不是在其成员上调用
Equals

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

Negative0.Equals(-0.0);//true

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

一般来说,您可以

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%的性能改进,但是。在发布模式下编译并从命令行运行后,没有显著差异



我也无法使用不安全的代码来提高性能,但这可能是因为我缺乏经验。

什么是负零,它与正零有何不同,它与零有何不同?从数学上讲,它们是相同的。IEEE 754可能没有。@Darin Dimitrov,你参加过数学分析课程吗?:P@klez是的,我有。我甚至有数学学位:-)@abelenky:如果你说的是IEEE double,虽然我为它的独创性喝彩,但我不确定它是否是最可读的方法:)如果你把1.0改为double.Epsilon,我想可能没问题:)@Jon-我加了一个零检查。奇怪的是,1/double.Epsilon的计算结果是无穷大的。@Anthony Pegram,不是真的。ε是非规范化的,因此它小于2**(最小指数)。对于非常大的值,没有非规范化的等价物,因此倒数太大,无法表示。看起来你的解决方案比我的快25倍。作为额外的奖励,没有额外的开销:)你必须热爱
不安全的
代码的威力有时:)@leppie-现在这太棒了:
返回*((长*)价值)它并不是很糟糕,因为(a)它封装得很好,(b)如果用户不能依赖IEEE浮点,那么即使是现有的-0.0也不能依赖。更好的抽象应该是
boolisnegative(double x){return BitConverter.doubleToInt64位(x)<0;}
。这个函数应该在标准的
Math
类中,我在另一个答案中看到了它,我想知道为什么它不在MiscUtil中。但是如果你说它很糟糕……出于某种原因,当我使用启用优化的x86目标构建它并在Visual Studio之外运行它时,passing
-0.0
将返回
false
。哦,这似乎是一个折衷的性能问题。我要指出,if语句混淆了JIT优化器。现在它假定值等于零,而不是负零,并直接用fldz而不是加载值加载它。因为那样更快。您也将在自己的代码中对此进行激烈的斗争。更新后。不幸的是,要完全实现ECMAScript标准,我必须了解
-0.0
。我看到标准正面冲突。祝你好运。
ValueType.Equals
并不总是执行逐位比较。不幸的是,它选择这样做的确切条件没有文档记录,并且可能在CLR版本/平台之间有所不同。整数没有明显的正零和负零。