2010-09-16 17 views
21

Aquí está la salida para el programa siguiente.Cuál debe ser el valor épsilon cuando se realiza una comparación igual de valor doble

value is : 2.7755575615628914E-17 
Double.compare with zero : 1 
isEqual with zero : true 

Mi pregunta es, lo que debe ser un valor de épsilon? ¿Hay alguna manera robusta de obtener el valor, en lugar de elegir un número desde el cielo?


package sandbox; 

/** 
* 
* @author yccheok 
*/ 
public class Main { 

    /** 
    * @param args the command line arguments 
    */ 
    public static void main(String[] args) { 
     double zero = 1.0/5.0 + 1.0/5.0 - 1.0/10.0 - 1.0/10.0 - 1.0/10.0 - 1.0/10.0; 
     System.out.println("value is : " + zero); 
     System.out.println("Double.compare with zero : " + Double.compare(zero, 0.0)); 
     System.out.println("isEqual with zero : " + isEqual(zero, 0.0)); 
    } 

    public static boolean isEqual(double d0, double d1) { 
     final double epsilon = 0.0000001; 
     return d0 == d1 ? true : Math.abs(d0 - d1) < epsilon; 
    } 
} 
+1

@ Chek, su pregunta no está clara, ¿qué espera de epsilon? – mhshams

Respuesta

8

La respuesta a su segunda pregunta es no. La magnitud del error de precisión finita máquina puede ser arbitrariamente grande:

public static void main(String[] args) { 
    double z = 0.0; 
    double x = 0.23; 
    double y = 1.0/x; 
    int N = 50000; 
    for (int i = 0; i < N; i++) { 
     z += x * y - 1.0; 
    } 
    System.out.println("z should be zero, is " + z); 
} 

Esto da ~5.55E-12, pero si aumenta N se puede conseguir casi cualquier nivel de error que desea.

Hay una gran cantidad de investigaciones pasadas y actuales sobre cómo escribir algoritmos numéricamente estables. Es un problema difícil

+0

¿Es 0.23 un número mágico? –

+0

No, solo un ejemplo de un número donde x * (1.0/x) no es igual a 1. – mob

+1

¿No deberíamos llamar a 0.23 como 'número mágico'? Como puedo usar 0.24 también, ¿verdad? Pensé que si podíamos elegir un número arbitrario, generalmente lo llamamos 'número mágico'. –

7

No hay un valor correcto. Debe calcularlo en relación con la magnitud de los números involucrados. Lo que básicamente está tratando es una cantidad de dígitos significativos, no una magnitud específica. Si, por ejemplo, sus números están en el rango de 1e-100, y sus cálculos deben mantener aproximadamente 8 dígitos significativos, entonces su épsilon debería estar alrededor de 1e-108. Si hicieras los mismos cálculos en números en el rango de 1e + 200, entonces tu épsilon sería alrededor de 1e + 192 (es decir, épsilon ~ = magnitud - dígitos significativos).

También me gustaría señalar que isEqual es un nombre deficiente, quiere algo así como isNearlyEQual. Por una razón, las personas razonablemente esperan que "igual" sea transitivo. Como mínimo, debe transmitir la idea de que el resultado ya no es transitivo; es decir, con su definición de isEqual, isEqual(a, c) puede ser falso, aunque isEqual(a, b) y isEqual(b, c) son verdaderos.

Editar: (respondiendo a los comentarios): Dije [...] "Si tus cálculos deben mantener aproximadamente 8 dígitos significativos, entonces tu epsilon debería ser ...". Básicamente, se trata de ver qué cálculos está haciendo, y cuánta precisión es probable que pierda en el proceso, para proporcionar una estimación razonable de cuán grande debe ser la diferencia antes de que sea significativa. Sin saber el cálculo que estás haciendo, no puedo adivinar eso.

En cuanto a la magnitud de epsilon: no, hace no tiene sentido para que siempre sea menor o igual a 1. Un número de coma flotante solo puede mantener una precisión limitada. En el caso de un punto flotante de doble precisión IEEE, la precisión máxima de que se puede representar es de aproximadamente 20 dígitos decimales. Eso significa que si comienzas con 1e + 200, la diferencia más pequeña absoluta de ese número que la máquina puede representar en total es aproximadamente 1e + 180 (y un doble puede representar números hasta ~ 1e + 308, en cuyo punto el más pequeño la diferencia que se puede representar es ~ 1e + 288).

+0

¿Por qué mi número es 1e-100, entonces épsilon debería estar alrededor de 1e-108. ¿Por qué 8 dígitos significativos? –

+0

@Coffin, ¿por qué epsilon es 1e + 192? ¿No es épsilon al menos menor que 1 y mayor que 0? –

11

I like (pseudo código, no lo haga java)

bool fuzzyEquals(double a, double b) 
{ 
    return abs(a - b) < eps * max(abs(a), abs(b)); 
} 

con epsilon ser un par de veces la máquina épsilon. Tome 10^-12 si no sabe qué usar.

Sin embargo, esto es bastante dependiente del problema.Si los cálculos que dan a y b son propensos a un error de redondeo, o implican muchas operaciones, o están ellos mismos dentro de cierta precisión (conocida), quiere tomar un épsilon más grande.

Este punto es utilizar relativo precisión, no absoluta.

+0

¿Puedo devolver abs (a - b)

+0

@Yan: sí, por supuesto. –

+0

Creo que debemos comparar entre abs (a) y abs (b), y tomar el valor mínimo para multiplicar con eps. Ver esencialmente Equal: http://jstock.cvs.sourceforge.net/viewvc/jstock/jstock/src/org/yccheok/jstock/portfolio/Utils.java?revision=1.15&view=markup –

1

Debería leer primero https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ primero.

Discute varias formas de comparar números de coma flotante: tolerancia absoluta, tolerancia relativa, distancia ulp. Es un buen argumento que la verificación ulp es el camino a seguir. El caso se basa en el argumento de que si desea verificar si dos números de punto flotante son iguales, debe tener en cuenta la distancia entre los flotantes representables. En otras palabras, debe verificar si los dos números están dentro de e flotan entre sí.

Los algoritmos se dan en C, pero se pueden traducir a Java usando java.lang.Double#doubleToLongBits y java.lang.Float#floatToIntBits para implementar la conversión de tipos flotantes a enteros. Además, con java> 1.5 hay métodos ulp(double)ulp(float) y para java> 1.6 nextUp(double)nextUp(float)nextAfter(double, double)nextAfter(float, float) que son útiles para cuantificar la diferencia entre dos números de coma flotante.

+0

el artículo señala que está obsoleto y este es el reemplazo: https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ – clausavram

1

Hay dos conceptos involucrados aquí:

  1. Una unidad de precisión de la máquina: Double.ulp()
  2. Una máquina de precisión para un determinado double d: Double.ulp(d)

Si llama a Double.ulp() obtendrá la máquina de precisión unidad, que es la precisión que puede esperar de una plataforma de hardware determinada ... ¡sea cual sea esta definición!

Si llama al Double.ulp(d), obtendrá la precisión de la máquina para double d. En otras palabras, cada double d tiene su precisión específica. Esto es más útil que el párrafo anterior.

Debe prestar especial atención a los detalles cuando realiza iteraciones que implican cálculos en cascada, es decir, cuando los resultados de los cálculos anteriores se emplean en el cálculo actual. Esto se debe a que los errores se acumulan en estas situaciones y pueden terminar, en ciertas circunstancias, entregando resultados que están muy lejos del verdadero valor que deberían ofrecer. En determinadas circunstancias, el tamaño del error acumulado puede ser incluso mayor que el valor verdadero. Ver algunos disastrous examples here.

En ciertos dominios comerciales, los errores de cálculo numérico simplemente no son aceptables.Dependiendo del dominio del negocio, sus regulaciones, requisitos y características, debe tomar enfoques alternativos para la opción simplista de emplear la aritmética de coma flotante (es decir: doubles o floats).

En el caso de Finanzas, por ejemplo, nunca utilice la aritmética de coma flotante. Nunca utilice doubles o floats cuando se trata de dinero. Nunca. Período. Puede emplear BigDecimal o fixed point arithmetic, según las circunstancias.

En el caso específico del procesamiento de precios de acciones, usted sabe que los precios tienen siempre 5 dígitos de precisión y, en este caso, fixed point arithmetic es suficiente y también ofrece el rendimiento máximo que puede obtener, que es muy fuerte y requisito común en este dominio comercial.

Si el dominio del negocio realmente requiere cálculos numéricos, en este caso debe asegurarse de mantener la propagación de errores bajo su estricto y cuidadoso control. Este es un tema largo, hay una serie de técnicas, y muy frecuentemente los desarrolladores pasan por alto el problema simplemente creyendo que hay una sola llamada mágica a un método que hace todo el trabajo duro por ellos. No, no es así. Tienes que investigar, hacer tu tarea y hacer todo el trabajo necesario para asegurarte de mantener los errores bajo control. Necesita comprender exactamente qué está pasando con los algoritmos numéricos que ha implementado.

Cuestiones relacionadas