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

8 minutos de lectura

avatar de usuario
Miembro orgulloso

lo sé UIKit usos 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 hace sentir enfermo:

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.

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

  • Es principalmente un problema para valores no enteros, donde los errores de redondeo ocurren fácilmente. escribió un entrada en el blog que describe cuándo ocurren los errores de redondeo y cómo estimar el tamaño de los posibles errores.

    – Hampus

    23 de enero de 2021 a las 7:22


avatar de usuario
Marca de alto rendimiento

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 probablemente sea segura. Sin embargo, es posible que lo muerdan 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:

areal = 0.0

creará (a menos que su idioma o sistema no funcione) un valor tal que (areal==0.0) devuelva verdadero pero otro cálculo 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 en el grado requerido , es mejor ceñirse al enfoque habitual de “igualdad tolerada”.

En los peores casos, la comparación descuidada de los valores 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 la computación se encuentre con el mundo real.

Para Angry Birds, no tan peligroso.

  • Realmente, 1.30 - 2*(0.65) es un ejemplo perfecto de una expresión que obviamente se evalúa como 0.0 si su compilador implementa IEEE 754, porque los dobles representados como 0.65 y 1.30 tienen los mismos significados, y la multiplicación por dos es obviamente exacta.

    – Pascal Cuoq

    6 de agosto de 2013 a las 17:04


  • Todavía obtengo reputación de este, así que cambié el fragmento del segundo ejemplo.

    – Marca de alto rendimiento

    16 dic 2013 a las 10:00

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

La respuesta correcta: CGPointEqualToPoint().

Una pregunta diferente: ¿Dos valores calculados son iguales?

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 verifique si están cerca. Haz 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.

Quiero dar una respuesta un poco diferente a las demás. 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.

¡El 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 usan flotadores para definir intervalos. ¡Y comparar si un flotador está dentro de un intervalo también definido por flotadores siempre está bien definido y solo necesita ser consistente, no exacto o preciso! Siempre que se pueda asignar un píxel (¡que también es un intervalo!), eso es todo lo que necesitan los gráficos.

Entonces, si desea probar si su punto está fuera de un [0..width[ range this is just fine. Just make sure you define inclusion consistently. For example always define inside is (x>=0 && x < width). The same goes for intersection or hit tests.

However, if you are abusing a graphics coordinate as some kind of flag, like for example to see if a window is docked or not, you should not do this. Use a boolean flag that is separate from the graphics presentation layer instead.

[The ‘right answer’ glosses over selecting K. Selecting K ends up being just as ad-hoc as selecting VISIBLE_SHIFT but selecting K is less obvious because unlike VISIBLE_SHIFT it is not grounded on any display property. Thus pick your poison – select K or select VISIBLE_SHIFT. This answer advocates selecting VISIBLE_SHIFT and then demonstrates the difficulty in selecting K]

Precisamente debido a los errores de ronda, 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 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 a la vista’ dependerá de las propiedades de visualización. Si puede establecer un límite superior en la pantalla (debería poder hacerlo); entonces escoge VISIBLE_SHIFT ser una fracción de ese límite superior.

Ahora, la ‘respuesta correcta’ se basa en K así que exploremos la elección K. La ‘respuesta correcta’ anterior dice:

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 con el cálculo del límite de error, haga que K sea un poco más grande que sus cálculos). decir que debería ser)

Así que necesitamos K. si conseguir K es más difícil, menos intuitivo que seleccionar mi VISIBLE_SHIFT entonces usted decidirá lo que funciona para usted. Encontrar K vamos a escribir un programa de prueba que analice un montón de K valores para que podamos ver cómo se comporta. Debería 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 debe 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 por el valor de ‘épsilon’, como he sugerido. Si está haciendo gráficos y ‘cero’ está destinado a ser un ‘cambio visible’, entonces examine sus activos visuales (imágenes, etc.) y juzgue qué épsilon puede ser.
  2. No intente ningún cálculo de punto flotante hasta que haya leído la referencia de la respuesta que no es de culto (y haya obtenido su doctorado en el proceso) y luego use su juicio no intuitivo para seleccionar K.

  • Un aspecto de la independencia de la resolución es que no se puede saber con certeza qué es un “desplazamiento visible” en tiempo de compilación. Lo que es invisible en una pantalla súper HD bien podría ser obvio en una pantalla diminuta. Uno debería al menos hacerlo una función del tamaño de la pantalla. O ponerle otro nombre.

    – Romain

    26 de abril de 2012 a las 13:53


  • Pero al menos seleccionar ‘desplazamiento visible’ se basa en propiedades de visualización (o marco) fáciles de entender, a diferencia de las K que es difícil y no intuitivo de seleccionar.

    – GoZoner

    28 de agosto de 2016 a las 13:56


¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad