valor que punto numeros notación normalizada norma maquina mantisa informatica flotante coma floating-point double floating-accuracy ieee-754

floating point - que - ¿Es posible obtener 0 restando dos números desiguales de coma flotante?



que es coma flotante en informatica (12)

¿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, ¿y si a y b están muy cerca, puede (ab) resultar en 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.


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 realmente realizar la división.


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 esa manera, no dependerá del soporte IEEE en ningún idioma.


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

Entonces a == b no se puede hacer con ningún valor doble de a y b, ¿cómo lidiar 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 realiza un "cálculo de doble valor" en una computadora, debe tratar con precisión, por lo que en lugar de hacer a==b , debe hacer un valor absolute_value(ab)<epsilon , y epsilon es relativo a lo que está modelando en esa vez en tu algoritmo. No puede tener un valor épsilon para toda su 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).

PD: hum, todo lo que respondo aquí está más o menos en otras respuestas y comentarios.


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

En particular, el lenguaje de programación Java requiere el soporte de números de punto flotante desnormalizados IEEE 754 y un flujo descendente gradual, lo que facilita probar las propiedades deseables de algoritmos numéricos particulares. Las operaciones de punto flotante no se "vuelven a cero" si el resultado calculado es un número desnormalizado.

Si una 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, el soporte IEEE 754 es opcional.

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


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

Con IEEE 754 está casi garantizado. Los compiladores de C o C ++ pueden realizar una operación con mayor precisión de la necesaria. Entonces, si ayb 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 por 0. En ese modo, si ayb son pequeños números normalizados 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 comparar números de punto flotante" es una programación de culto de carga. Entre las personas que tienen el mantra "necesitas un épsilon", la mayoría no tiene idea de cómo elegir ese épsilon correctamente.


En una implementación de punto flotante que se ajusta a IEEE-754, cada tipo de punto 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 poquito 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 ("desnormalizado") se usa solo para números muy pequeños que no son representables en el primer formato.

La circuitería para manejar el formato de punto flotante desnormalizado de manera eficiente es costosa, y no todos los procesadores lo incluyen. Algunos procesadores ofrecen una opción entre que las operaciones en números realmente pequeños sean mucho más lentas que las operaciones en otros valores, o que el procesador simplemente considere números que son demasiado pequeños para el formato normalizado como cero.

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


La división por cero no está definida, ya que el límite de los números positivos tiende al infinito, el límite de los números negativos tiende al infinito negativo.

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

double calculation(double a, double b) { if (a == b) { return nan(""); // C++ return Double.NaN; // Java } else { return 2 / (a - b); } }


La función suministrada puede devolver infinito:

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 el infinito incluso si el denominador no es cero.


No hay caso en el que una división por cero pueda suceder aquí.

El SMT Solver Z3 admite aritmética precisa de coma flotante IEEE. Pidamos a Z3 que encuentre los números a y b modo 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 cumple 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 sea NaN o infinito.

a == b se implementa como calidad fp.eq para que +0f y -0f comparen igual. La comparación con cero también se implementa usando fp.eq Dado que la pregunta tiene como objetivo evitar una división por cero, esta es la comparación adecuada.

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

Z3 Online aún no es compatible con la teoría 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 los casos (como NaN , -0f , +-inf ) y puede hacer preguntas arbitrarias. No es necesario interpretar y citar especificaciones. Incluso puede hacer preguntas mixtas flotantes y enteras como "¿es correcto este algoritmo int log2(float) ?".


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

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

EDITAR:

Como Bathsheba comentó correctamente, hay algunas excepciones:

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

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

Entonces, si a y b son Double.NaN , alcanzará la cláusula else, pero como NaN - NaN también devuelve NaN , no se dividirá por cero.


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

Para comparar flotadores para la igualdad sensatamente, debe verificar si el valor está "lo suficientemente cerca" del mismo valor:

if ((first >= second - error) || (first <= second + error)


Puedo pensar en un caso en el que podrías hacer que esto suceda. Aquí hay una muestra análoga en la base 10; en realidad, esto sucedería en la base 2, por supuesto.

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

Imagine por conveniencia que tenemos una unidad de coma flotante que opera en la 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 redondearse internamente en algún momento, pero en el caso anterior, solo 1 dígito adicional internamente evitará cualquier problema.