2009-10-23 25 views
23

En algún punto de un algoritmo necesito comparar el valor flotante de una propiedad de una clase con un flotador. Así que hago esto:Extraño problema al comparar flotadores en el objetivo-C

if (self.scroller.currentValue <= 0.1) { 
} 

donde currentValue es una propiedad flotante.

Sin embargo, cuando tengo igualdad y self.scroller.currentValue = 0.1, la instrucción if no se cumple y el código no se ejecuta. Descubrí que puedo arreglar esto lanzando 0.1 para flotar. De esta manera:

if (self.scroller.currentValue <= (float)0.1) { 
} 

Esto funciona bien.

¿Alguien puede explicarme por qué sucede esto? ¿Está 0.1 definido como un doble por defecto o algo así?

Gracias.

+4

Véase también, "Lo que todo informático debe saber sobre Floating Point-Aritmética:" http://docs.sun.com/source/ 806-3568/ncg_goldberg.html –

+0

Para aquellos de ustedes (especialmente @alastair) que han trabajado para mejorar mi respuesta, no estoy seguro de que pueda mejorarse. Estoy de acuerdo que fue incorrecto y probablemente peligroso. Lo he borrado Por favor, vea la respuesta de James Snook para una exploración mucho más profunda de este problema no trivial. –

+0

@RobNapier Tengo que decir que creí que valía la pena mantener la respuesta, con las correcciones añadidas, pero comprendo su perspectiva al respecto. Disculpas si mi edición parecía un poco agresiva, solo quería aclarar cuál era el problema. – alastair

Respuesta

30

creo, no habiendo encontrado la norma que dice así, que cuando se compara un float a un double la float es echado a un double antes comparando. Los números de punto flotante sin un modificador se consideran double en C.

Sin embargo, en C no hay una representación exacta de 0.1 en carrozas y dobles. Ahora, usar un flotador te da un pequeño error. Usar un doble te da un error aún menor. El problema ahora es que al convertir el float en un double, se carga el mayor error del float. Por supuesto, no se han ido a comparar igual ahora.

En lugar de usar (float)0.1 puede usar 0.1f que es un poco más agradable de leer.

1

Generalmente, en cualquier idioma, no se puede contar realmente con la igualdad de los tipos float. En su caso, ya que parece que tiene más control, parece que 0.1 no está flotando de forma predeterminada. Probablemente pueda averiguarlo con sizeof (0.1) (frente a sizeof (self.scroller.currentValue).

+0

¡esa es una buena idea! No pensé en sizeof() – Dimitris

+0

sizeof revela que 0.1 es un doble. Todavía es muy extraño que no obtenga igualdad cuando ambos tienen el valor 0.10000 ¿no es así? – Dimitris

+0

@Dimitris, no es tan extraño. Ver la respuesta de @ MarkPowell. –

4

Los dobles y los flotantes tienen valores diferentes para el almacén de mantisas en binario (el flotante es de 23 bits, el doble de 54). casi nunca será igual.

The IEEE Float Point article on wikipedia puede ayudar a entender esta distinción.

4

En C, un literal de coma flotante como 0.1 es un doble, no un flotante. Dado que los tipos de elementos de datos que se comparan son diferentes, la comparación se realiza en el tipo más preciso (doble). En todas las implementaciones que conozco, float tiene una representación más corta que el doble (generalmente se expresa como algo así como 6 decimales decimales). Además, la aritmética está en binario, y 1/10 no tiene una representación exacta en binario.

Por lo tanto, toma una flotación 0.1, que pierde precisión, extendiéndola al doble, y esperando que se compare igual a un 0.1 doble, que pierde menos precisión.

Supongamos que estamos haciendo esto en decimal, con float de tres dígitos y el doble de seis, y estamos comparando con 1/3.

Tenemos el valor flotante almacenado en 0.333. Lo estamos comparando con un doble con valor 0.333333. Convertimos el flotador 0.333 a doble 0.333000, y lo encontramos diferente.

+0

Siguiendo este pensamiento en decimal (que tiene diferentes números duros que binarios, pero el concepto es el mismo) encontrará que (1/3) * 3! = 1, sin importar cuántos dígitos (finitos) elija. Esta es la razón por la que hacer todas tus matemáticas en float o double realmente no soluciona el problema. –

+0

Derecha. Por supuesto, puede obtener arbitrariamente cerca de 1 usando suficientes dígitos, por lo que las pruebas para acercarse mucho funcionan mucho mejor que las pruebas de igualdad. El verdadero problema aquí es que las pruebas de igualdad de punto flotante no funcionan en general. –

+0

De acuerdo. Por lo tanto, la advertencia disponible (que recomiendo encender) para evitar que accidentalmente use la igualdad de punto flotante. –

4

0.1 es realmente un valor muy difícil de almacenar binario.En base 2, 1/10 es la fracción infinitamente repitiendo

0.0001100110011001100110011001100110011001100110011... 

Como varios ha señalado, la comparación tiene que hecho con una constante de la misma precisión.

6

El problema es que, como ha sugerido en su pregunta, está comparando una carroza con una doble.

Hay un problema más general al comparar flotadores, esto sucede porque cuando se hace un cálculo en un número de coma flotante, el resultado del cálculo puede no ser exactamente el esperado. Es bastante común que el último bit del flotador resultante sea incorrecto (aunque la inexactitud puede ser mayor que solo el último bit). Si usa == para comparar dos flotadores, entonces todos los bits tienen que ser iguales para que los flotadores sean iguales. Si su cálculo arroja un resultado ligeramente inexacto, entonces no se compararán cuando usted lo espera. En lugar de comparar los valores de esta manera, puede compararlos para ver si son casi iguales. Para hacer esto, puede tomar la diferencia positiva entre los flotadores y ver si es más pequeño que un valor dado (llamado épsilon).

Para elegir un buen épsilon necesita comprender un poco acerca de los números de coma flotante. Los números de coma flotante funcionan de manera similar a representar un número para un número dado de cifras significativas. Si trabajamos hasta 5 cifras significativas y su cálculo da como resultado que el último dígito del resultado sea incorrecto, entonces 1.2345 tendrá un error de + -0.0001, mientras que 1234500 tendrá un error de + -100. Si siempre basa su margen de error en el valor 1.2345, entonces su rutina de comparación será idéntica a == para todos los valores superiores a 10 (cuando se usa decimal). Esto es peor en binario, todos son valores mayores que 2. Esto significa que el épsilon que elegimos tiene que ser relativo al tamaño de los flotantes que estamos comparando.

FLT_EPSILON es la distancia entre 1 y el siguiente punto flotante más cercano. Esto significa que puede ser un buen épsilon elegir si su número está entre 1 y 2, pero si su valor es mayor que 2 usando este épsilon no tiene sentido porque la brecha entre 2 y el próximo float más cercano es mayor que épsilon. Entonces, tenemos que elegir un épsilon relativo al tamaño de nuestros flotadores (ya que el error en el cálculo es relativo al tamaño de nuestros flotadores).

Una buena (más o menos) de punto flotante comparar rutina se ve algo como esto:.

bool compareNearlyEqual (float a, float b, unsigned epsilonMultiplier)  
{ 
    float epsilon; 
    /* May as well do the easy check first. */ 
    if (a == b) 
    return true; 

    if (a > b) { 
    epsilon = scalbnf(1.0f, ilogb(a)) * FLT_EPSILON * epsilonMultiplier; 
    } else { 
    epsilon = scalbnf(1.0, ilogb(b)) * FLT_EPSILON * epsilonMultiplier; 
    } 

    return fabs (a - b) <= epsilon; 
} 

Esta rutina de comparación compara los flotadores en relación con el tamaño de la más grande de flotación aprobada en scalbnf(1.0f, ilogb(a)) * FLT_EPSILON encuentra la brecha entre a y el siguiente flotador más cercano. Luego, se multiplica por epsilonMultiplier, por lo que se puede ajustar el tamaño de la diferencia, dependiendo de cuán impreciso sea el resultado del cálculo.

Usted puede hacer un simple compareLessThan rutina de este tipo:

bool compareLessThan (float a, float b, unsigned epsilonMultiplier) 
{ 
    if (compareNearlyEqual (a, b, epsilonMultiplier) 
    return false; 

    return a < b; 
} 

También podría escribir una función muy similar compareGreaterThan.

Vale la pena señalar que la comparación de flotadores como este puede no ser siempre lo que usted desea. Por ejemplo, esto nunca encontrará que un float está cerca de 0 a menos que sea 0. Para solucionar esto, necesitaría decidir qué valor creía que era cercano a cero, y escribir una prueba adicional para esto.

En ocasiones, las imprecisiones que obtenga no dependerán del tamaño del resultado de un cálculo, sino que dependerán de los valores que ponga en un cálculo.Por ejemplo, sin(1.0f + (float)(200 * M_PI)) dará un resultado mucho menos preciso que sin(1.0f) (los resultados deben ser idénticos). En este caso, su rutina de comparación debería tener en cuenta el número que ingresó en el cálculo para conocer el margen de error de la respuesta.

-1

Convertir en una cadena, a continuación, comparar:

NSString* numberA = [NSString stringWithFormat:@"%.6f", a]; 
NSString* numberB = [NSString stringWithFormat:@"%.6f", b]; 

return [numberA isEqualToString: numberB]; 
Cuestiones relacionadas