2010-02-12 24 views
6

Estoy trabajando en un proyecto de visualización de computación científica & en C# /. NET, y usamos double s para representar todas las cantidades físicas. Puesto que los números de punto flotante siempre incluyen un poco de redondeo, tenemos métodos sencillos para hacer comparaciones de igualdad, tales como:Tratando con los errores de coma flotante en .NET

static double EPSILON = 1e-6; 

bool ApproxEquals(double d1, double d2) { 
    return Math.Abs(d1 - d2) < EPSILON; 
} 

bastante estándar.

Sin embargo, constantemente nos encontramos teniendo que ajustar la magnitud de EPSILON cuando nos encontramos con situaciones en las que el error de cantidades "iguales" es mayor de lo que habíamos anticipado. Por ejemplo, si multiplica 5 grandes double juntos y luego los divide 5 veces, perderá mucha precisión. Ha llegado al punto en que no podemos hacer que EPSILON sea demasiado grande o nos dará falsos positivos, pero también obtenemos falsos negativos.

En general, nuestro enfoque ha sido buscar más algoritmos numéricamente estables para trabajar, pero el programa es muy computacional y hay mucho que hemos podido hacer.

¿Alguien tiene alguna buena estrategia para resolver este problema? He analizado un poco el tipo Decimal, pero estoy preocupado por el rendimiento y realmente no sé lo suficiente sobre él para saber si resolvería el problema o si lo ocultaría. Estaría dispuesto a aceptar un golpe de rendimiento moderado (digamos, 2x) yendo a Decimal si solucionara estos problemas, pero el rendimiento es definitivamente una preocupación y dado que el código está mayormente limitado por la aritmética de coma flotante, no creo es una preocupación irracional. He visto personas que citan una diferencia de 100 veces, lo que definitivamente sería inaceptable.

Además, cambiar a Decimal tiene otras complicaciones, como la falta general de soporte en la biblioteca Math, por lo que tendríamos que escribir nuestra propia función de raíz cuadrada, por ejemplo.

¿Algún consejo?

EDIT: Por cierto, el hecho de que estoy usando un epsilon constante (en lugar de una comparación relativa) no es el punto de mi pregunta. Acabo de poner eso como un ejemplo, no es en realidad un snippit de mi código. Cambiar a una comparación relativa no supondría una diferencia para la pregunta, porque el problema surge al perder precisión cuando los números se vuelven muy grandes y luego pequeños nuevamente. Por ejemplo, podría tener un valor de 1000 y luego hacer una serie de cálculos que deberían dar como resultado exactamente el mismo número, pero debido a la pérdida de precisión, en realidad tengo 1001. Si luego voy a comparar esos números, no lo hago. Importa mucho si uso una comparación relativa o absoluta (siempre que haya definido las comparaciones de una manera que sea significativa para el problema y la escala).

De todos modos, como sugirió Mitch Wheat, el reordenamiento de los algoritmos ayudó a resolver los problemas.

Respuesta

4

Esto no es un problema exclusivo de .NET. La estrategia para reducir la pérdida de precisión es reordenar los cálculos para que pueda multiplicar grandes cantidades por cantidades pequeñas y sumar/restar cantidades de tamaños similares (sin cambiar la naturaleza del cálculo, obviamente).

En su ejemplo, en lugar de multiplicar 5 grandes cantidades juntas y luego dividir entre 5 grandes cantidades, vuelva a ordenar dividir cada cantidad grande por uno de los divisores, y luego multiplique estos 5 resultados parciales.

De interés?(Si aún no lo ha leído): What Every Computer Scientist Should Know About Floating-Point Arithmetic

+0

@nobugz: si lo sientes así, borra y edita tu respuesta y yo eliminaré el voto a favor. –

+0

@ High-Performance Mark: ¿este comentario fue para OP? –

+0

Bueno, he estado revisando nuestros algoritmos y haciendo algunos reordenamientos como sugirió, y parece haber ayudado. Empecé a trabajar en ese artículo, ¡aunque no es exactamente una lectura ligera! Gracias por la ayuda. –

1

Debido a los números reales forma en que se representaba generalmente, Usted puede hacer esto en C (y probablemente en insegura C#):

if (llabs(*(long long)&x - *(long long)&y) <= EPSILON) { 
    // Close enough 
} 

Esto es, obviamente, no -portable y probablemente una mala idea, pero tiene la ventaja significativa de independencia de escala. Es decir, EPSILON puede ser una pequeña constante como 1, 10 o 100 (dependiendo de su tolerancia deseada), y manejará los errores de redondeo proporcionales correctamente independientemente del exponente.

Descargo de responsabilidad: Esta es mi invención privada, y no ha sido examinada por alguien con una pista (como, por ejemplo, un matemático con experiencia en aritmética discreta).

+0

Permítanme reformular eso: ¿cómo es eso mejor que la técnica original de tolerancia de coma flotante (épsilon)? –

+0

Epsilon normalmente tiene que ser escalado en proporción a la magnitud de los números que espera tratar, lo que lo hace vulnerable a sorpresas en la magnitud de los datos reales. Este es precisamente el problema planteado en la pregunta. La comparación de la representación entera de los números no es vulnerable a tales problemas de escalado (tenga en cuenta que la elección de EPSILON en mi respuesta está determinada por la tolerancia deseada, no por la escala esperada de las entradas). La respuesta de John Knoeller es la mejor respuesta segura y portátil, pero es más cara (aunque probablemente sea marginal). –

2

Su mejor respuesta es siempre mejores algoritmos, por supuesto. Pero me parece que si sus valores no están todos dentro de un par de órdenes de magnitud de 1, el uso de un épsilon fijo no es una buena estrategia. Lo que quiere hacer en su lugar es asegurarse de que los valores sean iguales dentro de una precisión razonable.

// are values equal to within 12 (or so) digits of precision? 
// 
bool ApproxEquals(double d1, double d2) { 
    return Math.Abs(d1 - d2) < (Math.Abs(d1) * 1e-12); 
} 

Si esto fuera C++, entonces usted podría también tirar algunos trucos para comparar la mantisa y el exponente por separado, pero no puedo pensar en ninguna manera de hacer esto de manera segura en el código unmanged.

+0

Como he agregado una edición de la pregunta, esto realmente no aborda la pregunta que tenía. Pero es verdad que una comparación relativa es apropiada cuando las comparaciones se hacen sobre números con distintos órdenes de magnitud. –

Cuestiones relacionadas