2011-01-19 8 views
36

Inicialmente pensé que Math.Sign sería la manera correcta de hacerlo, pero después de ejecutar una prueba parece que trata a -0.0 y +0.0 de la misma manera.¿Cómo puedo probar el cero negativo?

+7

¿Qué es * cero negativo * y cómo es diferente a * cero positivo * y cómo es diferente de * cero *? Matemáticamente, son lo mismo. * IEEE 754ically * tal vez no. –

+0

Bit a nivel, ¿cómo difiere la representación de -0.0 de +0.0? 0x800000 y 0x000000? – Davidann

+3

@Darin Dimitrov, ¿alguna vez asistió a un curso de análisis matemático? : P –

Respuesta

42

Aquí está una manera horrible truco de hacerlo:

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

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

Básicamente esa es la prueba para el patrón de bits exacta de -0.0, pero sin tener que codificar la misma.

+3

Parece que su solución es aproximadamente 25 veces más rápida que la mía. – ChaosPandion

+0

Como bono adicional, sin gastos adicionales :) A veces tiene que amar el poder del código "inseguro" :) – leppie

+0

@leppie - Ahora esto es increíble: 'return * (((long *) & value));' – ChaosPandion

14

Después de un poco de búsqueda finalmente llegué al Section 7.7.2 de la especificación C# y se me ocurrió esta solución.

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

Mientras aplaudo el ingenio, no estoy seguro de que sea el enfoque más * legible :) –

+1

En realidad, -1 - cree que -double.Epsilon es cero negativo también ... –

+0

@Jon - Dag nabbit – ChaosPandion

8

El cero negativo tiene el bit de signo configurado. Por lo tanto:

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

Editar: como lo señaló OP, esto no funciona en el modo de lanzamiento. El optimizador x86 JIT toma en serio la sentencia if() y carga cero directamente en lugar de cargar el valor . Que de hecho es más eficiente. Pero eso hace que se pierda el cero negativo. El código debe ser de-sintonizado para evitar esto:

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

Este comportamiento es bastante típica de la fluctuación de fase 86 por cierto, que no se ocupa de casos de esquina muy bien cuando se optimiza el código de punto flotante. El jitter x64 es mucho mejor en ese sentido. Aunque podría decirse que no hay peor caso de esquina que darle significado al cero negativo. Ser prevenido

+0

Por alguna razón, cuando construyo esto con un objetivo de x86 con optimizaciones habilitadas y lo ejecuto fuera de Visual Studio, pasar '-0.0' devolverá' false'. Oh, y este parece ser el punto medio en cuanto a rendimiento. – ChaosPandion

+0

I repro, la instrucción if confunde el optimizador JIT. Ahora asume que el valor es igual a cero, cero negativo y lo carga directamente con fldz en lugar de cargar * valor *. Porque eso es más rápido. También lucharás contra esto en tu propio código. Publicación actualizada –

+0

Lamentablemente, para implementar completamente el estándar ECMAScript, debo tener en cuenta '-0.0'. – ChaosPandion

7

Aquí hay otro truco. Se aprovecha del hecho de que Equals en un struct va a hacer una comparación bit a bit en lugar de llamar Equals de sus miembros:

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

+0

'ValueType.Equals' no siempre realiza una comparación bit a bit. Y, desafortunadamente, las condiciones exactas cuando elige hacerlo no están documentadas, y pueden variar entre las versiones/plataformas de CLR. –

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

De manera más general, se puede hacer,

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

o, alternativamente, si lo prefiere,

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

La tarde da una mejora a un rendimiento del 50% aproximado en depuración, pero. Una vez compilado en modo de lanzamiento y ejecutado desde la línea de comando , no hay una diferencia significativa.

No pude generar una mejora en el rendimiento utilizando un código inseguro tampoco, pero esto puede deberse a mi inexperiencia.

Cuestiones relacionadas