2012-02-07 57 views
9

Estoy escribiendo un método de extensión para comparar dos flotantes usando un número determinado de puntos decimales (cifras significativas) para determinar si son iguales en lugar de una diferencia de tolerancia o porcentaje. Mirando a través de las otras preguntas con respecto a la comparación de flotantes, veo implementaciones complejas. ¿He simplificado demasiado o esto es válido?¿Es esta una comparación flotante válida que representa un número determinado de lugares decimales?

/// <summary> 
/// Determines if the float value is equal to (==) the float parameter according to the defined precision. 
/// </summary> 
/// <param name="float1">The float1.</param> 
/// <param name="float2">The float2.</param> 
/// <param name="precision">The precision. The number of digits after the decimal that will be considered when comparing.</param> 
/// <returns></returns> 
public static bool AlmostEquals(this float float1, float float2, int precision = 2) 
{ 
    return (Math.Round(float1 - float2, precision) == 0); 
} 

Nota: estoy buscando una comparación de cifras decimales, no de tolerancia. No quiero que 1,000,000 igualen 1,000,001.

+7

Escribiría un conjunto de pruebas de unidad para verificar si su algoritmo está bien para sus necesidades. –

+7

Me encanta este nombre de método: ** AlmostEquals ** ... – gdoron

+0

He escrito algunas pruebas de unidad y pasan con los valores que he proporcionado, pero me gustaría el consejo de una audiencia con una comprensión más profunda de la flotación implementación/comportamiento. – Kim

Respuesta

2

Sobre la base de la respuesta de @ de hecho y algunos comentarios en la pregunta y la respuesta que se le ocurrió

public static bool AlmostEquals(this float float1, float float2, int precision = 2) 
{ 
    float epsilon = Math.Pow(10.0, -precision) 
    return (Math.Abs(float1-float2) <= epsilon); 
} 

Esto tiene el benfit de aceptar cualquier número entero, se puede comprobar si hay> 1,0 precisión mediante el uso de precisión = -x, donde x es el poder de 10 para contrastar.

También recomendaría hacer la precisión predeterminada = 3, que le daría una precisión de hasta un décimo de un centavo, de forma predeterminada, si este método se utilizó para finanzas.

+0

Escogió esto como la respuesta porque aborda la idea de preocuparse por el número de decimales y expande el método para permitir valores negativos para la precisión. – Kim

+0

Apoyos para @infact para 'return (Math.Abs ​​(float1-float2) <= precision);' – EtherDragon

+3

Está * NOT * funcionando, la sección C# incluso cita un ejemplo: http://msdn.microsoft.com/en -us/library/75ks3aby.aspx Debido a la pérdida de precisión [...] es posible que el método Round (Double, Int32) no parezca redondear los valores del punto medio al valor par más cercano en la posición decimal de los dígitos. Esto se ilustra en el siguiente ejemplo, donde 2.135 se redondea a 2.13 en lugar de 2.14. Esto ocurre porque internamente el método multiplica el valor por 10 dígitos, y la operación de multiplicación en este caso sufre una pérdida de precisión. –

0

Si el usuario quiere "comparar dos flotadores usando un número determinado de puntos decimales (cifras significativas)" y en realidad esto significa que tenemos una función

AlmostEquals (14.3XXXXXXXX, 14.3YYYYYYY, 1) == verdadero para todos los posibles XXX y YYY y el último parámetro es el decimal después del punto decimal.

no es un simple pero desafortunada respuesta:

No es posible programar esta función que cumplirá este contrato. Es posible programar algo que a menudo dará el resultado correcto, pero no se puede prever cuándo será el caso, por lo que la función no tiene ningún valor.

Las soluciones que se dan aquí ya romper con AlmostEquals (0.06f, 0.14f, 1) = 0, pero cierto! = 1.

¿Por qué? La primera razón es la sensibilidad extrema. Por ejemplo: 0.0999999999 .... y 0.100000 ... 1 tienen dígitos diferentes en primer lugar, pero son casi indistinguibles en la diferencia, son casi exactamente iguales. Cualquiera que sea la función mítica, no puede permitir incluso pequeñas diferencias en el cálculo.

La segunda razón es que realmente queremos calcular con los números. Utilicé VC 2008 con C# para imprimir los valores correctos de la función Math.pow. El primero es el parámetro de precisión, el segundo el valor hexadecimal del flotante resultante y el tercero es el valor decimal exacto de.

1 3dcccccd 0,100000001490116119384765625

2 3c23d70a 0,00999999977648258209228515625

3 3a83126f 0,001000000047497451305389404296875

4 38d1b717 0,0000999999974737875163555145263671875

5 3727c5ac 0.00000999999974737875163555145263671875

6 358637bd 9.999999974752427078783512115478515625E-7

Como se puede ver, la secuencia de 0,1, 0,01, 0,001 etc. produce números que son excelentes aproximaciones, pero son o bien ligeramente demasiado pequeño o demasiado grande.

¿Qué pasa si hacemos cumplir que el lugar dado debe tener el dígito correcto? Permite enumerar los 16 valores binarios de 4 bits

0.0 
0.0625 
0.125 
0.1875 
0.25 
0.3125 
0.375 
0.4375 
0.5 
0.5625 
0.625 
0.6875 
0.75 
0.8125 
0.875 
0.9375 

16 diferentes números binarios debe ser capaz de ser suficiente para 10 números decimales si queremos calcular únicamente con un solo lugar después del punto decimal. Mientras que 0.5 es exactamente igual, aplicar el mismo dígito decimal significa que 0.4 necesita 0.4375 y 0.9 necesita 0.9375, presentando errores severos.

La violación de la primera condición de sensibilidad extrema significa que no puede hacer nada razonable con tales números. Si supiera que el lugar decimal de un número tiene un cierto valor, no necesitaría calcular en primer lugar.

El # documentación C incluso cita un ejemplo: http://msdn.microsoft.com/en-us/library/75ks3aby.aspx

Notas para los llamadores

Debido a la pérdida de precisión que puede resultar de que representa valores decimales como números de punto flotante o realizar aritmética operaciones en valores de punto flotante, en algunos casos el método Round (Double, Int32) no parece redondear los valores de punto medio al valor más cercano de en la posición decimal de dígitos. Esto se ilustra en el siguiente ejemplo de , donde 2.135 se redondea a 2.13 en lugar de 2.14. Esto ocurre porque internamente el método multiplica el valor por 10 dígitos, y la operación de multiplicación en este caso sufre una pérdida de precisión .

+0

Su premisa es incorrecta. Mientras que los números de coma flotante * son * aproximaciones, es ciertamente posible predecir el grado de error en esa aproximación, y siempre que lo sepa, también sabrá si su comparación es válida o no. –

+0

Es posible predecir el grado de error para una conversión decimal-binaria y viceversa, pero debido al problema, la resolución necesaria puede ser tan pequeña como el número de punto flotante más pequeño, rompiendo el código. El OP quiere que AlmostEquals (10.0,10.0XXXXXX, 1) == true, permitiendo los números 10.0000000..1 y 10.0999999999999 ... como entrada válida. Las rutinas de conversión de punto flotante eligen el siguiente punto flotante, pero puede ser 9.999999999 o 11.10000001. Ahora, con sumo cuidado, puede programar constructores que proporcionen el número decimal más pequeño que se ajuste, pero no puede agregarlos, etc., porque se rompen. –

+0

+1 @ThorstenS. para una respuesta y elaboración muy refinada sobre este asunto. – XAMlMAX

Cuestiones relacionadas