java - float - comparando valores flotantes/dobles usando el operador==
long vs double java (9)
Aquí está el código real de la implementación de Commons Math:
private static final int SGN_MASK_FLOAT = 0x80000000;
public static boolean equals(float x, float y, int maxUlps) {
int xInt = Float.floatToIntBits(x);
int yInt = Float.floatToIntBits(y);
if (xInt < 0)
xInt = SGN_MASK_FLOAT - xInt;
if (yInt < 0)
yInt = SGN_MASK_FLOAT - yInt;
final boolean isEqual = Math.abs(xInt - yInt) <= maxUlps;
return isEqual && !Float.isNaN(x) && !Float.isNaN(y);
}
Esto le da el número de flotadores que pueden representarse entre sus dos valores en la escala actual, que debería funcionar mejor que un épsilon absoluto.
La herramienta de revisión de código que utilizo se queja con lo siguiente cuando comienzo a comparar dos valores flotantes utilizando un operador de igualdad. ¿Cuál es la forma correcta y cómo hacerlo? ¿Hay alguna función auxiliar (commons- *) por ahí que pueda reutilizar?
Descripción
No se pueden comparar valores de punto flotante con el operador igual (==)
Explicación
La comparación de valores de punto flotante utilizando los operadores de igualdad (==) o de desigualdad (! =) No siempre es precisa debido a errores de redondeo.
Recomendación
Compare los dos valores flotantes para ver si están cerca del valor.
float a;
float b;
if(a==b)
{
..
}
El tipo float
es un valor aproximado : hay una parte de exponente y una parte de valor con precisión finita.
Por ejemplo:
System.out.println((0.6 / 0.2) == 3); // false
El riesgo es que un pequeño error de redondeo puede hacer una comparación false
, cuando matemáticamente debería ser true
.
La solución es comparar los flotadores que permiten que una pequeña diferencia siga siendo "igual":
static float e = 0.00000000000001f;
if (Math.abs(a - b) < e)
Apache commons-math al rescate: MathUtils. (Doble x, doble y, int maxUlps)
Devuelve verdadero si ambos argumentos son iguales o están dentro del rango de error permitido (inclusive). Dos números flotantes se consideran iguales si hay (maxUlps - 1) (o menos) números de punto flotante entre ellos, es decir, dos números de punto flotante adyacentes se consideran iguales.
En primer lugar, algunas cosas a tener en cuenta:
- La forma "estándar" de hacer esto es elegir una épsilon constante, pero las épsilons constantes no funcionan correctamente para todos los rangos de números.
- Si desea utilizar un epsilon
sqrt(EPSILON)
constantesqrt(EPSILON)
la raíz cuadrada del epsilon defloat.h
es generalmente un buen valor. (Esto viene de un infame "libro naranja" cuyo nombre se me escapa en este momento). - La división de punto flotante será lenta, por lo que probablemente querrá evitar comparaciones incluso si se comporta como elegir un épsilon hecho a medida para las magnitudes de los números.
¿Qué quieres hacer realmente? algo como esto:
Compare cuántos números de punto flotante representables difieren los valores por.
Este código proviene de this artículo realmente genial de Bruce Dawson. El artículo ha sido actualizado here . La principal diferencia es que el artículo anterior rompe la regla de alias estricto. (lanzar punteros flotantes a int puntero, desreferenciación, escritura, retroceso). Si bien el purista de C / C ++ señalará rápidamente la falla, en la práctica esto funciona, y considero que el código es más legible. Sin embargo, el nuevo artículo usa sindicatos y C / C ++ mantiene su dignidad. Para mayor brevedad, doy el código que rompe el alias estricto a continuación.
// Usable AlmostEqual function
bool AlmostEqual2sComplement(float A, float B, int maxUlps)
{
// Make sure maxUlps is non-negative and small enough that the
// default NAN won''t compare as equal to anything.
assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024);
int aInt = *(int*)&A;
// Make aInt lexicographically ordered as a twos-complement int
if (aInt < 0)
aInt = 0x80000000 - aInt;
// Make bInt lexicographically ordered as a twos-complement int
int bInt = *(int*)&B;
if (bInt < 0)
bInt = 0x80000000 - bInt;
int intDiff = abs(aInt - bInt);
if (intDiff <= maxUlps)
return true;
return false;
}
La idea básica en el código anterior es notar primero que, dado el formato de punto flotante IEEE 754, {sign-bit, biased-exponent, mantissa}
, los números están ordenados lexicográficamente si se interpretan como ints de magnitud con signo. Es decir, el bit de signo se convierte en el bit de signo, y el exponente siempre supera por completo a la mantisa en la definición de la magnitud del flotador y porque es el primero en determinar la magnitud del número interpretado como un int.
Entonces, interpretamos la representación de bits del número de punto flotante como un int de magnitud con signo. Luego, convertimos las entradas de magnitud firmada en entradas de complemento de dos, restándolas de 0x80000000 si el número es negativo. Luego simplemente comparamos los dos valores como lo haríamos con cualquiera de las dos partes del complemento firmadas, y viendo cuántos valores difieren. Si esta cantidad es menor que el umbral que elige para cuántos flotantes representables los valores pueden diferir y seguir siendo considerados iguales, entonces usted dice que son "iguales". Tenga en cuenta que este método permite correctamente que los números "iguales" difieran en valores más grandes para flotadores de mayor magnitud, y en valores más pequeños para flotadores de menor magnitud.
Hay muchos casos en que uno quiere considerar dos números de punto flotante como iguales solo si son absolutamente equivalentes, y una comparación "delta" sería incorrecta. Por ejemplo, si f es una función pura), y uno sabe que q = f (x) y y === x, entonces debe saber que q = f (y) sin tener que computarlo. Lamentablemente el == tiene dos defectos en este sentido.
Si un valor es cero positivo y el otro cero negativo, se compararán como iguales aunque no sean necesariamente equivalentes. Por ejemplo, si f (d) = 1 / d, a = 0 y b = -1 * a, entonces a == b pero f (a)! = F (b).
Si cualquiera de los valores es un NaN, la comparación siempre resultará falsa, incluso si un valor se asignó directamente del otro.
Aunque hay muchos casos en los que la verificación de los números de punto flotante para la equivalencia exacta es correcta y correcta, no estoy seguro de ningún caso en el que el comportamiento real de ==
deba considerarse preferible. Podría decirse que todas las pruebas de equivalencia deben realizarse a través de una función que realmente pruebe la equivalencia (por ejemplo, comparando formas a nivel de bits).
Probé esto basándose en la forma en que Java implementa == para los dobles. Primero se convierte a la forma de entero largo IEEE 754 y luego realiza una comparación a nivel de bits. Double también proporciona la doubleToLongBits () estática para obtener la forma entera. Usando el ajuste de bits, puede "redondear" la mantisa del doble agregando 1/2 (un bit) y truncando.
De acuerdo con la observación de supercat, la función primero intenta una comparación simple == y solo redondea si eso falla. Aquí está lo que se me ocurrió con algunos comentarios (con suerte) útiles.
Hice algunas pruebas limitadas, pero no puedo decir que haya intentado todos los casos de vanguardia. Además, no probé el rendimiento. No debería ser tan malo.
Acabo de darme cuenta de que esta es esencialmente la misma solución que ofreció Dmitri. Quizás un poco más conciso.
static public boolean nearlyEqual(double lhs, double rhs){
// This rounds to the 6th mantissa bit from the end. So the numbers must have the same sign and exponent and the mantissas (as integers)
// need to be within 32 of each other (bottom 5 bits of 52 bits can be different).
// To allow ''n'' bits of difference create an additive value of 1<<(n-1) and a mask of 0xffffffffffffffffL<<n
// e.g. 4 bits are: additive: 0x10L = 0x1L << 4 and mask: 0xffffffffffffffe0L = 0xffffffffffffffffL << 5
//int bitsToIgnore = 5;
//long additive = 1L << (bitsToIgnore - 1);
//long mask = ~0x0L << bitsToIgnore;
//return ((Double.doubleToLongBits(lhs)+additive) & mask) == ((Double.doubleToLongBits(rhs)+additive) & mask);
return lhs==rhs?true:((Double.doubleToLongBits(lhs)+0x10L) & 0xffffffffffffffe0L) == ((Double.doubleToLongBits(rhs)+0x10L) & 0xffffffffffffffe0L);
}
La siguiente modificación maneja el cambio en el caso de signo donde el valor está en cualquier lado de 0.
return lhs==rhs?true:((Double.doubleToLongBits(lhs)+0x10L) & 0x7fffffffffffffe0L) == ((Double.doubleToLongBits(rhs)+0x10L) & 0x7fffffffffffffe0L);
Quiere que los compares con la precisión que necesitas. Por ejemplo, si necesita que los primeros 4 dígitos decimales de sus flotantes sean iguales, entonces usaría:
if(-0.00001 <= a-b && a-b <= 0.00001)
{
..
}
O:
if(Math.abs(a-b) < 0.00001){ ... }
Donde se agrega la precisión deseada a la diferencia de los dos números y se compara con el doble de precisión deseada.
Lo que creas que sea más legible. Yo prefiero el primero, ya que muestra claramente la precisión que está permitiendo en ambos lados.
a = 5.43421
y b = 5.434205
pasará la comparación
Use commons-lang
org.apache.commons.lang.math.NumberUtils#compare
También matemática-común (en su situación la solución más apropiada):
http://commons.apache.org/math/apidocs/org/apache/commons/math/util/MathUtils.html#equals(double, double)
IBM tiene una recomendación para comparar dos flotantes, utilizando la división en lugar de la resta, esto hace que sea más fácil seleccionar un épsilon que funcione para todos los rangos de entrada.
if (abs(a/b - 1) < epsilon)
En cuanto al valor de épsilon, usaría 5.96e-08
como se indica en esta tabla de Wikipedia , o tal vez 2 veces ese valor.
private static final float EPSILON = <very small positive number>;
if (Math.abs(a-b) < EPSILON)
...
Como el punto flotante le ofrece una precisión variable pero incontrolable (es decir, no puede establecer la precisión más que cuando elige usar double
y float
), tiene que elegir su propia precisión fija para las comparaciones.
Tenga en cuenta que esto ya no es un verdadero operador de equivalencia, ya que no es transitivo. Puede obtener fácilmente a
igual b
y b
es igual a c
pero a
no es igual a c
.
Edición: también tenga en cuenta que si a
es negativo y b
es un número positivo muy grande, la resta puede desbordarse y el resultado será infinito negativo, pero la prueba seguirá funcionando, ya que el valor absoluto de infinito negativo es infinito positivo, que Ser más grande que EPSILON
.