¿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 la 0
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.
Respuestas:
En Java,
a - b
nunca es igual a0
ifa != b
. Esto se debe a que Java exige operaciones de punto flotante IEEE 754 que admiten números desnormalizados. De la especificación :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 se
2 / (a - b)
desborde, por ejemplo cona = 5e-308
yb = 4e-308
.fuente
(a,b) = (3,1)
=>2/(a-b) = 2/(3-1) = 2/2 = 1
Si esto es cierto con el punto flotante IEEE, no lo séComo solución alternativa, ¿qué pasa con lo siguiente?
De esa manera no dependerá del soporte IEEE en ningún idioma.
fuente
a=b
, no deberías volver0
. Dividir0
en IEEE 754 te da infinito, no una excepción. Estás evitando el problema, por lo que regresar0
es un error que espera suceder. Considere1/x + 1
. Six=0
, eso resultaría1
, no el valor correcto: infinito.0
no es realmente el problema. Esto es lo que hace el OP en la pregunta. Puede poner una excepción o lo que sea apropiado para la situación en esa parte del bloque. Si no le gusta regresar0
, eso debería ser una crítica de la pregunta. Ciertamente, hacer lo que hizo el OP no garantiza un voto negativo a la respuesta. Esta pregunta no tiene nada que ver con cálculos posteriores después de que se complete la función dada. Por lo que sabes, los requisitos del programa requieren que regreses0
.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 manera
a == b
devolvería cierto es que sia
yb
contener los mismos bits exacta. Si difieren solo en el bit menos significativo, la diferencia entre ellos no será 0.EDITAR:
Como Bathsheba comentó correctamente, hay algunas excepciones:
"Ningún número se compara" falso consigo mismo, pero tendrá patrones de bits idénticos.
-0.0 se define para comparar verdadero con +0.0, y sus patrones de bits son diferentes.
Entonces, si ambos
a
yb
sonDouble.NaN
, alcanzará la cláusula else, pero comoNaN - NaN
también regresaNaN
, no se dividirá por cero.fuente
No hay caso en el que una división por cero pueda ocurrir aquí.
El SMT Solver Z3 admite aritmética precisa de coma flotante IEEE. Pidamos a Z3 que encuentre números
a
yb
tal quea != b && (a - b) == 0
: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 es válido 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 seaNaN
infinita.a == b
se implementa comofp.eq
calidad para que+0f
y se-0f
compare igual. La comparación con cero se implementa utilizandofp.eq
también. 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ía sido una forma de hacera - 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:
Usando Z3 a responder a las preguntas IEEE flotador es agradable porque es difícil pasar por alto casos (como
NaN
,-0f
,+-inf
) y se puede hacer preguntas arbitrarias. No es necesario interpretar y citar especificaciones. Incluso puede hacer preguntas mixtas flotantes y enteras como "¿esint log2(float)
correcto este algoritmo en particular ?".fuente
La función suministrada puede devolver infinito:
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.
fuente
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).
fuente
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 la 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.
fuente
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.
fuente
strictfp
no está habilitado, es posible que los cálculos produzcan valores que son demasiado pequeñosdouble
pero que caben en un valor de punto flotante de precisión extendida.strictfp
solo influye en los valores de "resultados intermedios", y estoy citando de docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.4 .a
yb
sondouble
variables, no resultados intermedios, por lo que sus valores son valores de doble precisión, por lo tanto, son múltiplos de 2 ^ -1074. La sustracción de estos dos valores de doble precisión es, en consecuencia, un múltiplo de 2 ^ -1074, por lo que el rango de exponente más amplio cambia la propiedad de que la diferencia es 0 si f a == b.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:
fuente
abs(first - second) < error
(o<= error
) es más fácil y más conciso.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.
fuente
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) está mal cuando tienes "demasiado" decimal, por ejemplo, cuando manejas un 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 yb = 1/3? Dependiendo de su SO vs FPU vs número vs idioma versus conteo de 3 después de 0, tendrá verdadero o falso.
De todos modos, si hace un "cálculo de doble valor" en una computadora, debe tratar con precisión, por lo que, en lugar de hacerlo
a==b
, debe hacerloabsolute_value(a-b)<epsilon
, y epsilon es relativo a lo que está modelando en ese momento en su 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.
fuente
Basado en la respuesta de @malarres y el comentario de @Taemyr, aquí está mi pequeña contribución:
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.
fuente