En primer lugar, los valores de coma flotante no son "aleatorios" en su comportamiento. La comparación exacta puede y tiene sentido en muchos usos del mundo real. Pero si va a usar coma flotante, debe saber cómo funciona. Errar al lado de asumir que el punto flotante funciona como números reales le dará un código que se rompe rápidamente. Errar al lado de suponer que los resultados de coma flotante tienen una gran confusión aleatoria asociada con ellos (como la mayoría de las respuestas aquí sugieren) obtendrá un código que parece funcionar al principio, pero termina teniendo errores de gran magnitud y casos de esquinas rotas.
En primer lugar, si desea programar con coma flotante, debe leer esto:
Lo que todo informático debe saber sobre la aritmética de coma flotante
Sí, léelo todo. Si es una carga demasiado pesada, debe usar enteros / punto fijo para sus cálculos hasta que tenga tiempo de leerlo. :-)
Ahora, dicho esto, los mayores problemas con las comparaciones exactas de coma flotante se reducen a:
El hecho de que muchos valores que puede escribir en la fuente, o leer con scanf
o strtod
, no existan como valores de coma flotante y se conviertan silenciosamente a la aproximación más cercana. De esto es de lo que hablaba la respuesta de demon9733.
El hecho de que muchos resultados se redondeen debido a que no tienen la precisión suficiente para representar el resultado real. Un ejemplo fácil donde puedes ver esto es agregar x = 0x1fffffe
y y = 1
como flotantes. Aquí, x
tiene 24 bits de precisión en la mantisa (ok) y y
solo tiene 1 bit, pero cuando los agrega, sus bits no están en lugares superpuestos, y el resultado necesitaría 25 bits de precisión. En cambio, se redondea ( 0x2000000
en el modo de redondeo predeterminado).
El hecho de que muchos resultados se redondeen debido a la necesidad de infinitos lugares para obtener el valor correcto. Esto incluye resultados racionales como 1/3 (con el que está familiarizado desde el decimal, donde toma infinitos lugares), pero también 1/10 (que también toma infinitos lugares en binario, ya que 5 no es una potencia de 2), así como resultados irracionales como la raíz cuadrada de cualquier cosa que no sea un cuadrado perfecto.
Doble redondeo. En algunos sistemas (particularmente x86), las expresiones de coma flotante se evalúan con mayor precisión que sus tipos nominales. Esto significa que cuando ocurre uno de los tipos de redondeo anteriores, obtendrá dos pasos de redondeo, primero un redondeo del resultado al tipo de mayor precisión, luego un redondeo al tipo final. Como ejemplo, considere lo que sucede en decimal si redondea 1.49 a un entero (1), en comparación con lo que sucede si primero lo redondea a un decimal (1.5) y luego redondea ese resultado a un entero (2). Esta es en realidad una de las áreas más desagradables para tratar en coma flotante, ya que el comportamiento del compilador (especialmente para compiladores defectuosos y no conformes como GCC) es impredecible.
Funciones trascendentales ( trig
, exp
, log
, etc.) no se especifican para tener resultados correctamente redondeados; el resultado solo se especifica como correcto dentro de una unidad en el último lugar de precisión (generalmente denominado 1ulp ).
Cuando escribe código de coma flotante, debe tener en cuenta lo que está haciendo con los números que podrían causar que los resultados sean inexactos, y hacer comparaciones en consecuencia. Muchas veces tendrá sentido comparar con un "épsilon", pero ese épsilon debe basarse en la magnitud de los números que está comparando , no en una constante absoluta. (En los casos en que una épsilon constante constante funcionaría, ¡eso es muy indicativo de que el punto fijo, no el punto flotante, es la herramienta adecuada para el trabajo!)
Editar: en particular, una verificación de épsilon relativa a la magnitud debería ser similar a:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y))
Donde FLT_EPSILON
es la constante de float.h
(reemplazarla con DBL_EPSILON
por double
s o LDBL_EPSILON
de long double
s) y K
es una constante que elija de tal manera que el error acumulado de sus cálculos es, sin duda limitado por K
las unidades en el último lugar (y si no está seguro de que tiene el error cálculo correcto a la derecha, haga K
unas veces más grande de lo que sus cálculos dicen que debería ser).
Finalmente, tenga en cuenta que si usa esto, es posible que necesite un cuidado especial cerca de cero, ya FLT_EPSILON
que no tiene sentido para los denormales. Una solución rápida sería hacerlo:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y) || fabs(x-y) < FLT_MIN)
y del mismo modo sustituir DBL_MIN
si se usan dobles.
fabs(x+y)
es problemático six
yy
(puede) tener un signo diferente. Aún así, una buena respuesta contra la corriente de las comparaciones de culto de carga.x
yy
tiene un signo diferente, no hay problema. El lado derecho será "demasiado pequeño", pero desdex
yy
tienen distinto signo, que no debe comparar la igualdad de todos modos. (A menos que sean tan pequeños como para ser denormales, pero luego el segundo caso lo atrapa)Dado que 0 es exactamente representable como un número de punto flotante IEEE754 (o usando cualquier otra implementación de números fp con los que he trabajado), la comparación con 0 es probablemente segura. Sin embargo, podría ser mordido si su programa calcula un valor (como
theView.frame.origin.x
) que tiene razones para creer que debería ser 0 pero que su cálculo no puede garantizar que sea 0.Para aclarar un poco, un cálculo como:
(a menos que su idioma o sistema esté roto) creará un valor tal que (areal == 0.0) devuelve verdadero pero otro cálculo como
podría no.
Si puede asegurarse de que sus cálculos producen valores que son 0 (y no solo que producen valores que deberían ser 0), entonces puede continuar y comparar los valores de fp con 0. Si no puede asegurarse del grado requerido , mejor se adhieren al enfoque habitual de "igualdad tolerada".
En los peores casos, la comparación descuidada de los valores de fp puede ser extremadamente peligrosa: piense en aviónica, orientación de armas, operaciones de plantas de energía, navegación de vehículos, casi cualquier aplicación en la que la computación se encuentre con el mundo real.
Para Angry Birds, no es tan peligroso.
fuente
1.30 - 2*(0.65)
es un ejemplo perfecto de una expresión que obviamente evalúa a 0.0 si su compilador implementa IEEE 754, porque los dobles representados como0.65
y1.30
tienen los mismos significados, y la multiplicación por dos es obviamente exacta.Quiero dar una respuesta un poco diferente a las demás. Son excelentes para responder a su pregunta como se indicó, pero probablemente no para lo que necesita saber o cuál es su verdadero problema.
¡El punto flotante en los gráficos está bien! Pero casi no hay necesidad de comparar flotadores directamente. ¿Por qué necesitarías hacer eso? Los gráficos utilizan flotadores para definir intervalos. ¡Y comparar si un flotador está dentro de un intervalo también definido por flotadores siempre está bien definido y simplemente necesita ser consistente, no exacto o preciso! Siempre y cuando se pueda asignar un píxel (¡que también es un intervalo!), Eso es todo lo que necesita el gráfico.
Entonces, si desea probar si su punto está fuera de un rango de [0..ancho [esto está bien. Solo asegúrese de definir la inclusión de manera consistente. Por ejemplo, siempre definir dentro es (x> = 0 && x <ancho). Lo mismo ocurre con las pruebas de intersección o acierto.
Sin embargo, si abusa de una coordenada gráfica como algún tipo de indicador, como por ejemplo para ver si una ventana está acoplada o no, no debe hacerlo. Utilice una bandera booleana que esté separada de la capa de presentación de gráficos en su lugar.
fuente
Comparar con cero puede ser una operación segura, siempre que el cero no sea un valor calculado (como se señaló en la respuesta anterior). La razón de esto es que cero es un número perfectamente representable en coma flotante.
Hablando de valores perfectamente representables, obtienes 24 bits de rango en una noción de potencia de dos (precisión simple). Entonces 1, 2, 4 son perfectamente representables, como lo son .5, .25 y .125. Mientras todos sus bits importantes estén en 24 bits, usted es dorado. Entonces 10.625 se puede representar con precisión.
Esto es genial, pero se desmoronará rápidamente bajo presión. Se me ocurren dos escenarios: 1) Cuando se trata de un cálculo. No confíes en sqrt (3) * sqrt (3) == 3. Simplemente no será así. Y probablemente no estará dentro de un épsilon, como sugieren algunas de las otras respuestas. 2) Cuando está involucrado cualquier no poder de 2 (NPOT). Por lo tanto, puede sonar extraño, pero 0.1 es una serie infinita en binario y, por lo tanto, cualquier cálculo que implique un número como este será impreciso desde el principio.
(Ah, y la pregunta original mencionaba las comparaciones con cero. No olvide que -0.0 también es un valor de punto flotante perfectamente válido).
fuente
[La 'respuesta correcta' pasa por alto la selección
K
. SeleccionarK
termina siendo tan ad-hoc como seleccionar,VISIBLE_SHIFT
pero seleccionarK
es menos obvio porque, a diferencia deVISIBLE_SHIFT
esto, no se basa en ninguna propiedad de visualización. Por lo tanto, elija su veneno: seleccioneK
o seleccioneVISIBLE_SHIFT
. Esta respuesta aboga por seleccionarVISIBLE_SHIFT
y luego demuestra la dificultad de seleccionarK
]Precisamente debido a errores redondos, no debe usar la comparación de valores 'exactos' para operaciones lógicas. En su caso específico de una posición en una pantalla visual, no puede importar si la posición es 0.0 o 0.0000000003; la diferencia es invisible para el ojo. Entonces su lógica debería ser algo como:
Sin embargo, al final, 'invisible a la vista' dependerá de las propiedades de su pantalla. Si puede hacer un límite superior de la pantalla (debería poder hacerlo); luego elige
VISIBLE_SHIFT
ser una fracción de ese límite superior.Ahora, la "respuesta correcta" se basa
K
así que exploremos la selecciónK
. La 'respuesta correcta' anterior dice:Entonces lo necesitamos
K
. Si obtenerK
es más difícil, menos intuitivo que seleccionar mi,VISIBLE_SHIFT
entonces usted decidirá qué funciona para usted. Para encontrarloK
, vamos a escribir un programa de prueba queK
observe un conjunto de valores para que podamos ver cómo se comporta. Debería ser obvio cómo elegirK
, si la "respuesta correcta" es utilizable. ¿No?Vamos a utilizar, como los detalles de 'respuesta correcta':
Probemos con todos los valores de K:
Ah, entonces K debería ser 1e16 o mayor si quiero que 1e-13 sea 'cero'.
Entonces, diría que tienes dos opciones:
K
.fuente
K
que es difícil y no intuitivo de seleccionar.La pregunta correcta: ¿cómo se comparan los puntos en Cocoa Touch?
La respuesta correcta: CGPointEqualToPoint ().
Una pregunta diferente: ¿Son dos valores calculados iguales?
La respuesta publicada aquí: no lo son.
¿Cómo verificar si están cerca? Si desea verificar si están cerca, no use CGPointEqualToPoint (). Pero no verifique si están cerca. Haga algo que tenga sentido en el mundo real, como verificar si un punto está más allá de una línea o si un punto está dentro de una esfera.
fuente
La última vez que verifiqué el estándar C, no era necesario que las operaciones de coma flotante en dobles (64 bits en total, mantisa de 53 bits) fueran más precisas que esa precisión. Sin embargo, parte del hardware podría realizar las operaciones en registros de mayor precisión, y el requisito se interpretó en el sentido de que no requiere borrar bits de orden inferior (más allá de la precisión de los números que se cargan en los registros). Por lo tanto, podría obtener resultados inesperados de comparaciones como esta dependiendo de lo que quedó en los registros de quien durmió allí por última vez.
Dicho esto, y a pesar de mis esfuerzos por eliminarlo cada vez que lo veo, el equipo donde trabajo tiene mucho código C que se compila usando gcc y se ejecuta en Linux, y no hemos notado ninguno de estos resultados inesperados en mucho tiempo . No tengo idea de si esto se debe a que gcc está borrando los bits de bajo orden para nosotros, los registros de 80 bits no se usan para estas operaciones en computadoras modernas, el estándar ha cambiado, o qué. Me gustaría saber si alguien puede citar capítulos y versos.
fuente
Puede usar dicho código para comparar flotante con cero:
Esto se comparará con una precisión de 0.1, suficiente para CGFloat en este caso.
fuente
int
sin asegurartheView.frame.origin.x
está dentro / cerca de ese rango deint
conductores a un comportamiento indefinido (UB), o en este caso, 1/100 del rango deint
.}
fuente
Estoy usando la siguiente función de comparación para comparar varios lugares decimales:
fuente
Yo diría que lo correcto es declarar cada número como un objeto, y luego definir tres cosas en ese objeto: 1) un operador de igualdad. 2) un método setAcceptableDifference. 3) el valor en sí. El operador de igualdad devuelve verdadero si la diferencia absoluta de dos valores es menor que el valor establecido como aceptable.
Puede subclasificar el objeto para adaptarlo al problema. Por ejemplo, las barras de metal redondas de entre 1 y 2 pulgadas pueden considerarse de igual diámetro si sus diámetros difieren en menos de 0.0001 pulgadas. Entonces llamaría a setAcceptableDifference con el parámetro 0.0001, y luego usaría el operador de igualdad con confianza.
fuente