punto online numericos normalizado normalizada norma metodos mantisa informatica flotante ejemplos objective-c ios c floating-point floating-accuracy

objective-c - online - punto flotante metodos numericos



¿Qué tan peligroso es comparar valores de punto flotante? (9)

Sé que UIKit usa CGFloat debido al sistema de coordenadas independiente de la resolución.

Pero cada vez que quiero comprobar si, por ejemplo, frame.origin.x es 0 , me siento frame.origin.x :

if (theView.frame.origin.x == 0) { // do important operation }

¿No es CGFloat vulnerable a falsos positivos cuando se compara con == , <= , >= , < , > ? Es un punto flotante y tienen problemas de imprecisión: 0.0000000000041 por ejemplo.

¿ Objective-C manejando esto internamente al comparar o puede suceder que un origin.x que se lee como cero no se compare con 0 como verdadero?


Compararse 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 punto flotante.

Hablando de valores perfectamente representables, obtienes 24 bits de rango en una noción de potencia de dos (precisión simple). Así que 1, 2, 4 son perfectamente representables, como lo son .5, .25 y .125. Mientras todos tus bits importantes estén en 24 bits, eres dorado. Así que 10.625 se puede representar con precisión.

Esto es genial, pero se deshará rápidamente bajo presión. Dos escenarios vienen a la mente: 1) Cuando se trata de un cálculo. No confíe en que 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 involucre un número como este será impreciso desde el principio.

(Ah, y la pregunta original mencionó las comparaciones a cero. No olvide que -0.0 también es un valor de punto flotante perfectamente válido).


Dado que 0 es exactamente representable como un número de punto flotante IEEE754 (o utilizando cualquier otra implementación de números fp con los que haya trabajado), la comparación con 0 es probablemente segura. Sin embargo, es posible que lo theView.frame.origin.x 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, una computación tal como:

areal = 0.0

(a menos que su idioma o sistema esté dañado) creará un valor tal que (areal == 0.0) devuelva verdadero pero otro cómputo como

areal = 1.386 - 2.1*(0.66)

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 el peor de los casos, la comparación descuidada de los valores de fp puede ser extremadamente peligrosa: piense en aviónica, guía de armas, operaciones de centrales eléctricas, navegación de vehículos, casi cualquier aplicación en la que el cómputo se encuentre con el mundo real.

Para Angry Birds, no es tan peligroso.


En primer lugar, los valores de punto 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 utilizar un punto flotante, debe ser consciente de cómo funciona. Suprimir por el lado de asumir que el punto flotante funciona como un número real le dará un código que se rompe rápidamente. Si se asume que los resultados de punto flotante tienen una gran confusión aleatoria asociada (como la mayoría de las respuestas que se sugieren aquí), 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 punto flotante, debe leer esto:

Lo que todo científico informático debe saber sobre la aritmética de punto flotante

Sí, lee todo. Si eso es una carga excesiva, debe usar números enteros / puntos fijos para sus cálculos hasta que tenga tiempo para leerlos. :-)

Ahora, dicho esto, los problemas más grandes con comparaciones de punto flotante exactas se reducen a:

  1. El hecho de que muchos valores que puede escribir en la fuente, o leer con scanf o strtod , no existen como valores de punto flotante y se convierte silenciosamente a la aproximación más cercana. De esto es de lo que hablaba la respuesta de demon9733.

  2. El hecho de que muchos resultados se redondean debido a que no tienen la precisión suficiente para representar el resultado real. Un ejemplo fácil donde se puede ver esto es agregar x = 0x1fffffe e y = 1 como flotadores. Aquí, x tiene 24 bits de precisión en la mantisa (ok) y y tiene solo 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 su lugar, se redondea (a 0x2000000 en el modo de redondeo predeterminado).

  3. El hecho de que muchos resultados se redondean debido a la necesidad de infinitos lugares para el valor correcto. Esto incluye tanto los resultados racionales como 1/3 (con el que estás familiarizado desde el decimal donde toma infinitos lugares) pero también 1/10 (que también tiene 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.

  4. Doble redondeo. En algunos sistemas (particularmente x86), las expresiones de punto 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, y 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 punto flotante, ya que el comportamiento del compilador (especialmente para compiladores con errores, no conformes como GCC) es impredecible.

  5. Las funciones trascendentales ( trig , exp , log , etc.) no se especifican para tener resultados redondeados correctamente; el resultado solo se especifica para que sea correcto dentro de una unidad en el último lugar de precisión (generalmente denominado 1ulp ).

Cuando está escribiendo un código de punto flotante, debe tener en cuenta lo que está haciendo con los números que podrían causar que los resultados sean inexactos, y hacer las comparaciones correspondientes. Muchas veces tendrá sentido comparar con un "épsilon", pero ése debería basarse en la magnitud de los números que está comparando , no en una constante absoluta. (En los casos en los que una épsilon de constante absoluta funcionaría, ¡eso es muy indicativo de que el punto fijo, no el punto flotante, es la herramienta adecuada para el trabajo!)

Edición: en particular, una comprobación de épsilon relativa a la magnitud debería tener el siguiente aspecto:

if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y))

Donde FLT_EPSILON es la constante de float.h (reemplácelo con DBL_EPSILON para el double s o LDBL_EPSILON para el long double s long double ) y K es una constante que usted elige de tal manera que el error acumulado de sus cálculos está definitivamente limitado por K unidades en el último lugar ( y si no está seguro de haber acertado en el cálculo del límite de error, haga que K un poco más grande de lo que dicen sus cálculos).

Finalmente, tenga en cuenta que si usa esto, es posible que necesite un cuidado especial cerca de cero, ya que FLT_EPSILON 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 de la misma manera sustituye a DBL_MIN si usa dobles.


La última vez que verifiqué el estándar C, no fue necesario que las operaciones de punto flotante en dobles (64 bits en total, mantisa de 53 bits) fueran precisas a más de esa precisión. Sin embargo, algunos equipos pueden realizar las operaciones en registros de mayor precisión, y se interpretó que el requisito significa que no hay ningún requisito para 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 haya quedado en los registros de quien haya dormido allí por última vez.

Dicho esto, y a pesar de mis esfuerzos por eliminarlo cada vez que lo veo, el equipo donde trabajo tiene un montón de código C que se compila con 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á limpiando los bits de orden inferior para nosotros, los registros de 80 bits no se utilizan para estas operaciones en las computadoras modernas, el estándar ha cambiado, o qué. Me gustaría saber si alguien puede citar el capítulo y el verso.


La pregunta correcta: ¿cómo se comparan los puntos en Cocoa Touch?

La respuesta correcta: CGPointEqualToPoint ().

Una pregunta diferente: ¿Son dos valores calculados los mismos?

La respuesta publicada aquí: No lo son.

¿Cómo comprobar si están cerca? Si desea verificar si están cerca, no use CGPointEqualToPoint (). Pero, no compruebe 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 está dentro de una esfera.


Puede usar dicho código para comparar float con cero:

if ((int)(theView.frame.origin.x * 100) == 0) { // do important operation }

Esto se comparará con una precisión de 0.1, lo suficiente para CGFloat en este caso.


Quiero dar una respuesta un poco diferente a las otras. Son excelentes para responder a su pregunta como se indica, pero probablemente no para lo que necesita saber o cuál es su problema real.

Punto flotante en 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 está siempre bien definido y simplemente necesita ser consistente, no preciso ni preciso! Siempre que se pueda asignar un píxel (¡que también es un intervalo!) Es todo lo que se necesita para gráficos.

Entonces, si desea probar si su punto está fuera de un [0..width [rango, esto está bien. Solo asegúrate de definir la inclusión consistentemente. Por ejemplo, siempre defina el interior es (x> = 0 && x <ancho). Lo mismo ocurre con las pruebas de intersección o golpe.

Sin embargo, si está abusando de una coordenada de gráficos como algún tipo de indicador, como por ejemplo, para ver si una ventana está acoplada o no, no debe hacer esto. Use una bandera booleana que esté separada de la capa de presentación de gráficos en su lugar.


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 adaptarse al problema. Por ejemplo, las barras redondas de metal entre 1 y 2 pulgadas pueden considerarse de igual diámetro si sus diámetros difieran 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.


[La ''respuesta correcta'' pasa por alto la selección de K La selección de K termina siendo tan ad-hoc como la selección de VISIBLE_SHIFT pero la selección de K es menos obvia porque a diferencia de VISIBLE_SHIFT no se basa en ninguna propiedad de pantalla. Por lo tanto, elija su veneno: seleccione K o seleccione VISIBLE_SHIFT . Esta respuesta aboga por seleccionar VISIBLE_SHIFT y luego demuestra la dificultad de seleccionar K ]

Precisamente debido a los errores de redondeo, 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, es posible que no importe si la posición es 0.0 o 0.0000000003: la diferencia es invisible para el ojo. Entonces tu lógica debería ser algo como:

#define VISIBLE_SHIFT 0.0001 // for example if (fabs(theView.frame.origin.x) < VISIBLE_SHIFT) { /* ... */ }

Sin embargo, al final, ''invisible para el ojo'' dependerá de las propiedades de su pantalla. Si puede limitar la pantalla (debería poder hacerlo); luego elija VISIBLE_SHIFT para que sea una fracción de ese límite superior.

Ahora, la "respuesta correcta" descansa sobre K así que exploremos la selección de K La "respuesta correcta" de arriba dice:

K es una constante que usted elige de tal manera que el error acumulado de sus cálculos definitivamente está delimitado por K unidades en el último lugar (y si no está seguro de haber acertado en el cálculo del límite de error, haga que K sea unas veces más grande que lo que sus cálculos decir que debería ser

Así que necesitamos a K Si obtener K es más difícil, menos intuitivo que seleccionar mi VISIBLE_SHIFT entonces decidirás qué es lo que funciona para ti. Para encontrar K , vamos a escribir un programa de prueba que analice un montón de valores de K para que podamos ver cómo se comporta. Debe ser obvio cómo elegir K , si la "respuesta correcta" es utilizable. ¿No?

Vamos a utilizar, como detalles de la ''respuesta correcta'':

if (fabs(x-y) < K * DBL_EPSILON * fabs(x+y) || fabs(x-y) < DBL_MIN)

Probemos todos los valores de K:

#include <math.h> #include <float.h> #include <stdio.h> void main (void) { double x = 1e-13; double y = 0.0; double K = 1e22; int i = 0; for (; i < 32; i++, K = K/10.0) { printf ("K:%40.16lf -> ", K); if (fabs(x-y) < K * DBL_EPSILON * fabs(x+y) || fabs(x-y) < DBL_MIN) printf ("YES/n"); else printf ("NO/n"); } } ebg@ebg$ gcc -o test test.c ebg@ebg$ ./test K:10000000000000000000000.0000000000000000 -> YES K: 1000000000000000000000.0000000000000000 -> YES K: 100000000000000000000.0000000000000000 -> YES K: 10000000000000000000.0000000000000000 -> YES K: 1000000000000000000.0000000000000000 -> YES K: 100000000000000000.0000000000000000 -> YES K: 10000000000000000.0000000000000000 -> YES K: 1000000000000000.0000000000000000 -> NO K: 100000000000000.0000000000000000 -> NO K: 10000000000000.0000000000000000 -> NO K: 1000000000000.0000000000000000 -> NO K: 100000000000.0000000000000000 -> NO K: 10000000000.0000000000000000 -> NO K: 1000000000.0000000000000000 -> NO K: 100000000.0000000000000000 -> NO K: 10000000.0000000000000000 -> NO K: 1000000.0000000000000000 -> NO K: 100000.0000000000000000 -> NO K: 10000.0000000000000000 -> NO K: 1000.0000000000000000 -> NO K: 100.0000000000000000 -> NO K: 10.0000000000000000 -> NO K: 1.0000000000000000 -> NO K: 0.1000000000000000 -> NO K: 0.0100000000000000 -> NO K: 0.0010000000000000 -> NO K: 0.0001000000000000 -> NO K: 0.0000100000000000 -> NO K: 0.0000010000000000 -> NO K: 0.0000001000000000 -> NO K: 0.0000000100000000 -> NO K: 0.0000000010000000 -> NO

Ah, entonces K debería ser 1e16 o mayor si quiero que 1e-13 sea ''cero''.

Entonces, yo diría que tienes dos opciones:

  1. Haga un cálculo simple de épsilon usando su juicio de ingeniería para el valor de ''épsilon'', como he sugerido. Si está haciendo gráficos y se supone que ''cero'' es un ''cambio visible'' que examinar sus recursos visuales (imágenes, etc.) y juzgar qué puede ser épsilon.
  2. No intente realizar cálculos de punto flotante hasta que haya leído la referencia de la respuesta no relacionada con el culto de la carga (y haya obtenido su Ph.D en el proceso) y luego utilice su criterio no intuitivo para seleccionar K