2010-05-24 12 views
8

En Java, la aritmética de coma flotante no se representa con precisión. Por ejemplo este código java:Manipulación y comparación de puntos flotantes en java

float a = 1.2; 
float b= 3.0; 
float c = a * b; 
if(c == 3.6){ 
    System.out.println("c is 3.6"); 
} 
else { 
    System.out.println("c is not 3.6"); 
} 

Imprime "c is not 3.6".

No me interesa la precisión más allá de 3 decimales (#. ###). ¿Cómo puedo lidiar con este problema para multiplicar flotantes y compararlos de manera confiable?

+1

Declarar flotantes como: 'float a = 1.2f;' y dobles como 'double d = 1.2d;' También en su instrucción if: 'if (c == 3.6f)' –

+1

Como adición a @bobah ' s respuesta, recomiendo mirar la función 'Math.ulp()'. – aeracode

Respuesta

6

Si está interesado en números de precisión fijos, debe utilizar un tipo de precisión fija como BigDecimal, no un tipo inherentemente aproximado (aunque de alta precisión) como float. Existen numerosas preguntas similares sobre el Desbordamiento de pila que entran en esto con más detalle, en muchos idiomas.

16

Es una regla general que el número de coma flotante no debe ser comparado como (a == b), sino más bien como (Math.abs(a-b) < delta) donde delta es un número pequeño.

Un valor de coma flotante que tiene un número fijo de dígitos en forma decimal no necesita tener un número fijo de dígitos en forma binaria.

adición para mayor claridad:

Aunque estricta == comparación de números de punto flotante tiene muy poco sentido práctico, la estricta < y > comparación, por el contrario, es un caso de uso válido (por ejemplo - la lógica de disparo cuando cierto valor excede el umbral: (val > threshold) && panic();)

+2

Recomendar la comparación usando una tolerancia es un consejo inapropiado porque disminuye los informes falsos de desigualdad a expensas de aumentar los informes falsos de igualdad, y no puede saber si eso es aceptable para una aplicación de la que no sabe nada. La aplicación podría estar "más interesada" en buscar la desigualdad que en buscar la igualdad o podría tener otras especificaciones que necesita cumplir. –

+2

@Eric - Cuando se trabaja con números flotantes no existe la noción de identidad o inequidad, solo existe una noción de distancia. Si en la fórmula que proporcioné en la respuesta reemplazas '<' with '>', obtendrás un criterio para comparar los números de coma flotante por inequidad en términos de distancia. La identidad en bits de la representación de los números de coma flotante en la memoria de la computadora no es de interés para la mayoría de las aplicaciones prácticas – bobah

+0

No se trata de los bits que representan un número; se trata de sus valores. La aritmética de coma flotante tiene igualdad. El estándar IEEE 754 define objetos de coma flotante para representar números específicos exactamente, no para representar intervalos. –

4

Creo que no tiene nada que ver con Java, sucede en cualquier número de coma flotante IEEE 754. Se debe a la naturaleza de la representación en coma flotante. Cualquier idioma que use el formato IEEE 754 tendrá el mismo problema.

Según lo sugerido por David anterior, debe utilizar el método abs de la clase java.lang.Math para obtener el valor absoluto (soltar el signo positivo/negativo).

Puede leer esto: http://en.wikipedia.org/wiki/IEEE_754_revision y también un buen libro de texto de métodos numéricos resolverá el problema lo suficiente.

public static void main(String[] args) { 
    float a = 1.2f; 
    float b = 3.0f; 
    float c = a * b; 
     final float PRECISION_LEVEL = 0.001f; 
    if(Math.abs(c - 3.6f) < PRECISION_LEVEL) { 
     System.out.println("c is 3.6"); 
    } else { 
     System.out.println("c is not 3.6"); 
    } 
} 
0

para comparar dos flotadores, f1 y f2 dentro precisión de #.### creo que tendría que hacer como esto:

((int) (f1 * 1000 + 0.5)) == ((int) (f2 * 1000 + 0.5)) 

f1 * 1000 ascensores 3.14159265... a 3141.59265, + 0.5 resultados en 3142.09265 y los (int) chuletas fuera de los decimales, 3142. Es decir, incluye 3 decimales y redondea el último dígito correctamente.

+0

Comparar usando un épsilon es mejor: considere lo que sucede si 'f1 == 3.1414999999999' y' f2 == 3.1415000000001'. –

+0

Mierda. Pensé que lo tenía :-) seguro. Estoy de acuerdo contigo. Comparar usando un épsilon es mucho mejor. Pero, ¿compara con precisión dos flotadores de sus 3 primeros decimales? – aioobe

2

Esta es una debilidad de todas las representaciones de coma flotante, y ocurre porque algunos números que parecen tener un número fijo de decimales en el sistema decimal, de hecho tienen un número infinito de decimales en el sistema binario. Entonces, lo que piensas que es 1.2 es algo así como 1.199999999997 porque cuando lo representas en binario tiene que cortar los decimales después de cierto número, y pierdes algo de precisión. Luego multiplicarlo por 3 en realidad da 3.5999999 ...

http://docs.python.org/py3k/tutorial/floatingpoint.html < - esto podría explicarlo mejor (incluso si es para Python, que es un problema común de la representación de punto flotante)

+1

+1 - * all * sistemas de números flotantes de precisión finita sufren este problema. No importa qué base elijas, algunos racionales no se pueden representar exactamente. –

2

Al igual que los otros escribieron:

Compare los flotadores con: if (Math.abs(a - b) < delta)

puede escribir un buen método para hacer esto:

public static int compareFloats(float f1, float f2, float delta) 
{ 
    if (Math.abs(f1 - f2) < delta) 
    { 
     return 0; 
    } else 
    { 
     if (f1 < f2) 
     { 
      return -1; 
     } else { 
      return 1; 
     } 
    } 
} 

/** 
* Uses <code>0.001f</code> for delta. 
*/ 
public static int compareFloats(float f1, float f2) 
{ 
    return compareFloats(f1, f2, 0.001f); 
} 

Por lo tanto, se puede utilizar de esta manera:

if (compareFloats(a * b, 3.6f) == 0) 
{ 
    System.out.println("They are equal"); 
} 
else 
{ 
    System.out.println("They aren't equal"); 
} 
2

estoy usando este fragmento de código en las pruebas unitarias para comparar si el resultado de 2 cálculos diferentes son los mismos, salvo errores matemáticos de punto flotante.

Funciona al observar la representación binaria del número de punto flotante. La mayor parte de la complicación se debe al hecho de que el signo de los números de coma flotante no es complemento de dos. Después de compensar eso, básicamente se reduce a una simple resta para obtener la diferencia en ULP (explicado en el comentario a continuación).

/** 
* Compare two floating points for equality within a margin of error. 
* 
* This can be used to compensate for inequality caused by accumulated 
* floating point math errors. 
* 
* The error margin is specified in ULPs (units of least precision). 
* A one-ULP difference means there are no representable floats in between. 
* E.g. 0f and 1.4e-45f are one ULP apart. So are -6.1340704f and -6.13407f. 
* Depending on the number of calculations involved, typically a margin of 
* 1-5 ULPs should be enough. 
* 
* @param expected The expected value. 
* @param actual The actual value. 
* @param maxUlps The maximum difference in ULPs. 
*/ 
public static void compareFloatEquals(float expected, float actual, int maxUlps) { 
    int expectedBits = Float.floatToIntBits(expected) < 0 ? 0x80000000 - Float.floatToIntBits(expected) : Float.floatToIntBits(expected); 
    int actualBits = Float.floatToIntBits(actual) < 0 ? 0x80000000 - Float.floatToIntBits(actual) : Float.floatToIntBits(actual); 
    int difference = expectedBits > actualBits ? expectedBits - actualBits : actualBits - expectedBits; 

    return !Float.isNaN(expected) && !Float.isNaN(actual) && difference <= maxUlps; 
} 

Aquí es una versión para double precisión flota:

/** 
* Compare two double precision floats for equality within a margin of error. 
* 
* @param expected The expected value. 
* @param actual The actual value. 
* @param maxUlps The maximum difference in ULPs. 
* @see Utils#compareFloatEquals(float, float, int) 
*/ 
public static void compareDoubleEquals(double expected, double actual, long maxUlps) { 
    long expectedBits = Double.doubleToLongBits(expected) < 0 ? 0x8000000000000000L - Double.doubleToLongBits(expected) : Double.doubleToLongBits(expected); 
    long actualBits = Double.doubleToLongBits(actual) < 0 ? 0x8000000000000000L - Double.doubleToLongBits(actual) : Double.doubleToLongBits(actual); 
    long difference = expectedBits > actualBits ? expectedBits - actualBits : actualBits - expectedBits; 

    return !Double.isNaN(expected) && !Double.isNaN(actual) && difference <= maxUlps; 
} 
2

Hay una clase de Apache en dobles comparando: org.apache.commons.math3.util.Precision

contiene algunas constantes interesantes: SAFE_MIN y EPSILON, que son las desviaciones máximas posibles al realizar operaciones aritméticas.

También proporciona los métodos necesarios para comparar, iguales o dobles redondos.

Cuestiones relacionadas