¿Es posible obtener 0 restando dos números desiguales de coma flotante?


¿Es posible obtener la división por 0 (o infinito) en el siguiente ejemplo?

public double calculation(double a, double b)
{
     if (a == b)
     {
         return 0;
     }
     else
     {
         return 2 / (a - b);
     }
}

En casos normales no lo hará, por supuesto. Pero ¿qué pasa si a y b están muy cerca, puede (a-b) resultar en ser 0 debido a la precisión del cálculo?

Tenga en cuenta que esta pregunta es para Java, pero creo que se aplicará a la mayoría de los lenguajes de programación.

Author: Graham Borland, 2015-02-12

12 answers

En Java, a - b nunca es igual a 0 si a != b. Esto se debe a que Java ordena operaciones de coma flotante IEEE 754 que admiten números desnormalizados. De la especificación :

En particular, el lenguaje de programación Java requiere soporte de números de coma flotante denormalizados IEEE 754 y flujo gradual, lo que hace que sea más fácil demostrar las propiedades deseables de algoritmos numéricos particulares. Las operaciones de coma flotante no "flush to zero" si el resultado calculado es un número desnormalizado.

Si un FPU funciona con números desnormalizados, restar números desiguales nunca puede producir cero (a diferencia de la multiplicación), también vea esta pregunta.

Para otros idiomas, depende. En C o C++, por ejemplo, la compatibilidad con IEEE 754 es opcional.

Dicho esto, es posible que la expresión 2 / (a - b) se desborde, por ejemplo con a = 5e-308 y b = 4e-308.

 131
Author: nwellnhof,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:00:00

Como solución alternativa, ¿qué pasa con lo siguiente?

public double calculation(double a, double b) {
     double c = a - b;
     if (c == 0)
     {
         return 0;
     }
     else
     {
         return 2 / c;
     }
}

De esta manera no depende del soporte IEEE en ningún idioma.

 51
Author: malarres,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-09 15:38:44

No obtendría una división por cero independientemente del valor de a - b, ya que la división en coma flotante por 0 no arroja una excepción. Devuelve el infinito.

Ahora, la única forma en que a == b devolvería true es si a y b contienen exactamente los mismos bits. Si difieren solo por el bit menos significativo, la diferencia entre ellos no será 0.

EDITAR:

Como Betsabé comentó correctamente, hay algunas excepciones:{[10]]}

  1. "No es un número compara " false consigo mismo pero tendrá patrones de bits idénticos.

  2. -0.0 se define para comparar true con + 0.0, y sus patrones de bits son diferentes.

Así que si ambos a y b son Double.NaN, alcanzarás la cláusula else, pero como NaN - NaN también devuelve NaN, no estarás dividiendo por cero.

 25
Author: Eran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-12 10:12:42

No hay caso donde una división por cero puede suceder aquí.

El Solucionador SMT Z3 soporta aritmética precisa de coma flotante IEEE. Pidamos a Z3 que encuentre números a y b tales que a != b && (a - b) == 0:

(set-info :status unknown)
(set-logic QF_FP)
(declare-fun b () (FloatingPoint 8 24))
(declare-fun a () (FloatingPoint 8 24))
(declare-fun rm () RoundingMode)
(assert
(and (not (fp.eq a b)) (fp.eq (fp.sub rm a b) +zero) true))
(check-sat)

El resultado es UNSAT. No hay tales números.

La cadena SMTLIB anterior también permite a Z3 elegir un modo de redondeo arbitrario (rm). Esto significa que el resultado se mantiene para todos los modos de redondeo posibles (de los cuales hay cinco). El resultado también incluye la posibilidad de que cualquiera de las variables en juego podría ser NaN o infinito.

a == b se implementa como fp.eq calidad de modo que +0f y -0f comparar igual. La comparación con cero se implementa usando fp.eq también. Dado que la pregunta está dirigida a evitar una división por cero, esta es la comparación apropiada.

Si la prueba de igualdad se implementó usando igualdad bitwise, +0f y -0f habría sido una forma de hacer a - b cero. Incorrecto la versión anterior de esta respuesta contiene detalles de modo sobre ese caso para los curiosos.

Z3 Online aún no apoya la teoría de FPA. Este resultado se obtuvo utilizando la última rama inestable. Se puede reproducir utilizando los enlaces. NET de la siguiente manera:

var fpSort = context.MkFPSort32();
var aExpr = (FPExpr)context.MkConst("a", fpSort);
var bExpr = (FPExpr)context.MkConst("b", fpSort);
var rmExpr = (FPRMExpr)context.MkConst("rm", context.MkFPRoundingModeSort());
var fpZero = context.MkFP(0f, fpSort);
var subExpr = context.MkFPSub(rmExpr, aExpr, bExpr);
var constraintExpr = context.MkAnd(
        context.MkNot(context.MkFPEq(aExpr, bExpr)),
        context.MkFPEq(subExpr, fpZero),
        context.MkTrue()
    );

var smtlibString = context.BenchmarkToSMTString(null, "QF_FP", null, null, new BoolExpr[0], constraintExpr);

var solver = context.MkSimpleSolver();
solver.Assert(constraintExpr);

var status = solver.Check();
Console.WriteLine(status);

Usar Z3 para responder preguntas flotantes de IEEE es bueno porque es difícil pasar por alto casos (como NaN, -0f, +-inf) y puedes hacer preguntas arbitrarias. No es necesario interpretar y citar especificaciones. Usted incluso puede hacer preguntas mixtas de flotadores y enteros como " ¿es correcto este algoritmo int log2(float) en particular?".

 17
Author: usr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-15 10:41:32

La función suministrada puede devolver infinity:

public class Test {
    public static double calculation(double a, double b)
    {
         if (a == b)
         {
             return 0;
         }
         else
         {
             return 2 / (a - b);
         }
    }    

    /**
     * @param args
     */
    public static void main(String[] args) {
        double d1 = Double.MIN_VALUE;
        double d2 = 2.0 * Double.MIN_VALUE;
        System.out.println("Result: " + calculation(d1, d2)); 
    }
}

La salida es Result: -Infinity.

Cuando el resultado de la división es demasiado grande para ser almacenado en un doble, se devuelve infinito incluso si el denominador es distinto de cero.

 12
Author: D Krueger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-09 15:39:01

En una implementación de coma flotante que se ajusta a IEEE-754, cada tipo de coma flotante puede contener números en dos formatos. Uno ("normalizado") se usa para la mayoría de los valores de coma flotante, pero el segundo número más pequeño que puede representar es solo un poco más grande que el más pequeño, por lo que la diferencia entre ellos no es representable en ese mismo formato. El otro formato ("denormalizado") se usa solo para números muy pequeños que no son representables en el primer formato.

Circuitos manejar el formato de coma flotante denormalizado de manera eficiente es costoso, y no todos los procesadores lo incluyen. Algunos procesadores ofrecen una opción entre tener operaciones en números realmente pequeños mucho más lento que las operaciones en otros valores, o tener el procesador simplemente considerar números que son demasiado pequeños para el formato normalizado como cero.

Las especificaciones de Java implican que las implementaciones deben soportar formato desnormalizado, incluso en máquinas donde hacerlo haría el código se ejecuta más lentamente. Por otro lado, es posible que algunas implementaciones puedan ofrecer opciones para permitir que el código se ejecute más rápido a cambio de un manejo ligeramente descuidado de los valores que, para la mayoría de los propósitos, serían demasiado pequeños para importar (en casos donde los valores son demasiado pequeños para importar, puede ser molesto tener cálculos con ellos que tomen diez veces más tiempo que los cálculos que sí importan, por lo que en muchas situaciones prácticas, flush-to-zero es más útil que la aritmética lenta pero precisa).

 6
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-12 17:04:09

En tiempos antiguos antes de IEEE 754, era muy posible que a != b no implicaba a-b != 0 y viceversa. Esa fue una de las razones para crear IEEE 754 en primer lugar.

Con IEEE 754 es casi garantizado. A los compiladores de C o C++ se les permite hacer una operación con mayor precisión de la necesaria. Así que si a y b no son variables sino expresiones, ¡entonces (a + b) != c no implica (a + b) - c != 0, porque a + b podría calcularse una vez con mayor precisión, y una vez sin mayor precisión.

Muchas FPU se pueden cambiar a un modo en el que no devuelven números desnormalizados, sino que los reemplazan con 0. En ese modo, si a y b son números normalizados diminutos donde la diferencia es menor que el número normalizado más pequeño pero mayor que 0, ¡a != b tampoco garantiza a = = b.

"Nunca compare números de coma flotante" es programación de culto de carga. Entre las personas que tienen el mantra "necesitas un epsilon", la mayoría no tienen idea de cómo elegir eso epsilon correctamente.

 5
Author: gnasher729,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-14 19:14:11

Nunca debe comparar flotadores o dobles para la igualdad; porque, realmente no puede garantizar que el número que asigna al flotador o doble sea exacto.

Para comparar flotadores de igualdad sanely, es necesario comprobar si el valor es "lo suficientemente cerca" del mismo valor:

if ((first >= second - error) || (first <= second + error)
 2
Author: aviad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-12 10:00:00

Se me ocurre un caso en el que podría ser capaz de causar que esto suceda. Aquí está una muestra análoga en base 10 - realmente, esto sucedería en base 2, por supuesto.

Los números en coma flotante se almacenan más o menos en notación científica, es decir, en lugar de ver 35.2, el número que se almacena sería más como 3.52e2.

Imagine por conveniencia que tenemos una unidad de coma flotante que opera en base 10 y tiene 3 dígitos de precisión. Qué sucede cuando resta 9.99 de 10.0?

1.00e2-9. 99e1

Shift para dar a cada valor el mismo exponente

1.00e2-0. 999e2

Redondear a 3 dígitos

1.00e2-1. 00e2

Uh oh!

Si esto puede suceder en última instancia depende del diseño de la FPU. Dado que el rango de exponentes para un doble es muy grande, el hardware tiene que redondear internamente en algún momento, pero en el caso anterior, solo 1 dígito adicional internamente evitará cualquier problema.

 2
Author: Keldor314,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-12 14:34:31

Basado en la respuesta de @malarres y el comentario de @Taemyr, aquí está mi pequeña contribución:

public double calculation(double a, double b)
{
     double c = 2 / (a - b);

     // Should not have a big cost.
     if (isnan(c) || isinf(c))
     {
         return 0; // A 'whatever' value.
     }
     else
     {
         return c;
     }
}

Mi punto es decir: la forma más fácil de saber si el resultado de la división es nan o inf es actualmente realizar la división.

 2
Author: Orace,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-09 15:40:02

La división por cero es indefinida, ya que el límite de los números positivos tiende al infinito, el limitado de los números negativos tiende al infinito negativo.

No estoy seguro si esto es C++ o Java ya que no hay ninguna etiqueta de lenguaje.

double calculation(double a, double b)
{
     if (a == b)
     {
         return nan(""); // C++

         return Double.NaN; // Java
     }
     else
     {
         return 2 / (a - b);
     }
}
 1
Author: Khaled.K,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-18 04:48:44

El problema central es que la representación por computadora de un doble (también conocido como float, o número real en lenguaje matemático) es incorrecta cuando se tiene "demasiado" decimal, por ejemplo cuando se trata de un doble que no se puede escribir como un valor numérico (pi o el resultado de 1/3).

Así que a==b no se puede hacer con cualquier valor doble de a y b, ¿cómo tratar con a==b cuando a=0.333 y b=1/3 ? Dependiendo de su sistema operativo vs FPU vs número vs idioma versus conteo de 3 después de 0, tendrá verdadero o falso.

De todos modos si haces "cálculo de doble valor" en una computadora, tienes que lidiar con la precisión, así que en lugar de hacer a==b, tienes que hacer absolute_value(a-b)<epsilon, y epsilon es relativo a lo que estás modelando en ese momento en tu algoritmo. No puedes tener un valor epsilon para toda tu doble comparación.

En resumen, cuando escribe a==b, tiene una expresión matemática que no se puede traducir en una computadora (para cualquier número de coma flotante).

PS: hum, todo lo que respondo aquí es todavía más o menos en otras respuestas y comentarios.

 1
Author: Jean Davy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-18 20:11:59