我如何testing负零?
最初,我认为Math.Sign
将是正确的路线,但在运行testing后,似乎将-0.0
和+0.0
视为相同。
这是一个糟糕的破解方式:
private static readonly long NegativeZeroBits = BitConverter.DoubleToInt64Bits(-0.0); public static bool IsNegativeZero(double x) { return BitConverter.DoubleToInt64Bits(x) == NegativeZeroBits; }
基本上这是testing-0.0的确切位模式,但不必硬编码。
经过一番search之后,我终于把它做到了C#规范的7.7.2节 ,并提出了这个解决scheme。
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指出的,这在Release模式下不起作用。 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抖动来说,这是非常典型的行为,它在优化浮点代码时不能很好地处理转angular情况。 在这方面,x64抖动要好得多。 虽然可以说没有比赋予负数零的含义更糟糕的angular落案例。 预先警告。
x == 0 && 1 / x < 0
这是另一个黑客。 它利用了这样一个事实,即一个struct
上的Equals
将进行按位比较,而不是在其成员上调用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
更一般地说,你可以做,
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; }
后者在debugging中提供了大约50%的性能提升。 一旦在释放模式下编译并从命令行运行, 则没有显着差异 。
我不能使用不安全的代码来提高性能,但这可能是由于我缺乏经验。