É possível obter divisão por 0 (ou infinito) no exemplo a seguir?
public double calculation(double a, double b)
{
if (a == b)
{
return 0;
}
else
{
return 2 / (a - b);
}
}
Em casos normais, isso não acontecerá, é claro. Mas, a
e se b
estiver muito próximo, pode (a-b)
resultar em 0
precisão do cálculo?
Observe que esta pergunta é para Java, mas acho que se aplicará à maioria das linguagens de programação.
Respostas:
Em Java,
a - b
nunca é igual a0
sea != b
. Isso ocorre porque o Java exige operações de ponto flutuante IEEE 754 que suportam números desnormalizados. Das especificações :Se uma FPU trabalha com números desnormalizados , subtrair números desiguais nunca pode produzir zero (ao contrário da multiplicação), também veja esta pergunta .
Para outros idiomas, isso depende. Em C ou C ++, por exemplo, o suporte ao IEEE 754 é opcional.
Dito isto, é possível que a expressão
2 / (a - b)
ultrapasse, por exemplo, coma = 5e-308
eb = 4e-308
.fonte
(a,b) = (3,1)
=>2/(a-b) = 2/(3-1) = 2/2 = 1
Se isso é verdade com IEEE ponto flutuante, eu não seiComo solução alternativa, o que dizer do seguinte?
Dessa forma, você não depende do suporte da IEEE em nenhum idioma.
fonte
a=b
você não deveria estar retornando0
. A divisão0
no IEEE 754 leva ao infinito, não uma exceção. Você está evitando o problema, portanto, retornar0
é um bug que está esperando para acontecer. Considere1/x + 1
. Sex=0
isso resultasse1
, não o valor correto: infinito.0
não é realmente o problema. É isso que o OP faz na pergunta. Você pode colocar uma exceção ou o que for apropriado para a situação nessa parte do bloco. Se você não gosta de voltar0
, isso deve ser uma crítica à pergunta. Certamente, fazer como o OP não garante uma resposta negativa à resposta. Esta questão não tem nada a ver com cálculos adicionais após a conclusão da função especificada. Pelo que você sabe, os requisitos do programa exigem retorno0
.Você não obteria uma divisão por zero, independentemente do valor de
a - b
, pois a divisão de ponto flutuante por 0 não gera uma exceção. Retorna o infinito.Agora, a única maneira de
a == b
retornar true é ifa
eb
contém exatamente os mesmos bits. Se eles diferirem apenas pelo bit menos significativo, a diferença entre eles não será 0.EDIT:
Como Bathsheba comentou corretamente, existem algumas exceções:
"Nenhum número se compara" false com ele mesmo, mas terá padrões de bits idênticos.
-0.0 é definido para comparar true com +0.0, e seus padrões de bits são diferentes.
Portanto, se ambos
a
eb
sãoDouble.NaN
, você alcançará a cláusula else, mas comoNaN - NaN
também retornaNaN
, você não estará dividindo por zero.fonte
Não há nenhum caso em que uma divisão por zero possa acontecer aqui.
O SMT Solver Z3 suporta aritmética precisa de ponto flutuante IEEE. Vamos pedir ao Z3 para encontrar números
a
eb
tais quea != b && (a - b) == 0
:O resultado é
UNSAT
. Não existem tais números.A sequência SMTLIB acima também permite que o Z3 escolha um modo de arredondamento arbitrário (
rm
). Isso significa que o resultado é válido para todos os modos de arredondamento possíveis (dos quais existem cinco). O resultado também inclui a possibilidade de qualquer uma das variáveis em jogo serNaN
ou infinita.a == b
é implementado comofp.eq
qualidade para que+0f
e-0f
comparar igual. A comparação com zero é implementada usandofp.eq
também. Como a pergunta visa evitar uma divisão por zero, esta é a comparação apropriada.Se o teste de igualdade foi implementado usando a igualdade bit a bit,
+0f
e-0f
tivesse sido uma maneira de fazera - b
zero. Uma versão anterior incorreta desta resposta contém detalhes do modo sobre esse caso para os curiosos.O Z3 Online ainda não suporta a teoria da FPA. Este resultado foi obtido usando o último ramo instável. Ele pode ser reproduzido usando as ligações do .NET da seguinte maneira:
Usando Z3 para responder a perguntas IEEE flutuador é bom porque é difícil ignorar casos (como
NaN
,-0f
,+-inf
) e você pode fazer perguntas arbitrárias. Não há necessidade de interpretar e citar especificações. Você pode até fazer perguntas inteiras de float e float, como "esteint log2(float)
algoritmo específico está correto?".fonte
A função fornecida pode realmente retornar o infinito:
A saída é
Result: -Infinity
.Quando o resultado da divisão é grande demais para ser armazenado em um dobro, o infinito é retornado mesmo se o denominador for diferente de zero.
fonte
Em uma implementação de ponto flutuante em conformidade com a IEEE-754, cada tipo de ponto flutuante pode conter números em dois formatos. Um ("normalizado") é usado para a maioria dos valores de ponto flutuante, mas o segundo menor número que ele pode representar é apenas um pouquinho maior que o menor e, portanto, a diferença entre eles não é representável no mesmo formato. O outro formato ("desnormalizado") é usado apenas para números muito pequenos que não são representáveis no primeiro formato.
Os circuitos para lidar com o formato de ponto flutuante desnormalizado com eficiência são caros e nem todos os processadores o incluem. Alguns processadores oferecem uma escolha entre ambos que têm operações em muito pequeno número seja muito mais lentas que operações em outros valores, ou fazer com que o processador simplesmente considere zero os números pequenos demais para o formato normalizado.
As especificações Java implicam que as implementações devem suportar o formato desnormalizado, mesmo em máquinas onde isso faria o código rodar mais lentamente. Por outro lado, é possível que algumas implementações ofereçam opções para permitir que o código seja executado mais rapidamente em troca de um tratamento levemente desleixado de valores que, na maioria dos casos, seriam muito pequenos para importar (nos casos em que valores são muito pequenos para importar, pode ser irritante ter cálculos com eles dez vezes mais do que os cálculos que importam; portanto, em muitas situações práticas, a descarga para zero é mais útil do que a aritmética lenta, mas precisa).
fonte
Antigamente, antes da IEEE 754, era bem possível que a! = B não implicasse ab! = 0 e vice-versa. Essa foi uma das razões para criar o IEEE 754 em primeiro lugar.
Com o IEEE 754, é quase garantido. Compiladores C ou C ++ podem executar uma operação com maior precisão do que o necessário. Portanto, se aeb não são variáveis, mas expressões, (a + b)! = C não implica (a + b) - c! = 0, porque a + b pode ser calculado uma vez com maior precisão e uma vez sem maior precisão.
Muitas FPUs podem ser alteradas para um modo em que não retornam números desnormalizados, mas os substituem por 0. Nesse modo, se aeb forem pequenos números normalizados em que a diferença seja menor que o menor número normalizado, mas maior que 0, a ! = b também não garante a == b.
"Nunca compare números de ponto flutuante" é a programação do culto à carga. Entre as pessoas que têm o mantra "você precisa de um epsilon", a maioria não tem idéia de como escolher esse epsilon corretamente.
fonte
Posso pensar em um caso em que você possa causar isso. Aqui está uma amostra análoga na base 10 - realmente, isso aconteceria na base 2, é claro.
Os números de ponto flutuante são armazenados mais ou menos em notação científica - ou seja, em vez de ver 35.2, o número sendo armazenado seria mais parecido com 3.52e2.
Imagine, por uma questão de conveniência, que temos uma unidade de ponto flutuante que opera na base 10 e tem 3 dígitos de precisão. O que acontece quando você subtrai 9,99 de 10,0?
1.00e2-9.99e1
Shift para dar a cada valor o mesmo expoente
1.00e2-0.999e2
Arredondar para 3 dígitos
1.00e2-1.00e2
Oh!
Se isso pode acontecer em última análise, depende do design da FPU. Como o intervalo de expoentes para um duplo é muito grande, o hardware precisa arredondar internamente em algum momento, mas no caso acima, apenas 1 dígito extra internamente impedirá qualquer problema.
fonte
strictfp
não estiver ativado, é possível que os cálculos produzam valores muito pequenos,double
mas que se ajustem a um valor de ponto flutuante de precisão estendida.strictfp
apenas influencia os valores de "resultados intermediários", e estou citando docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.4 .a
eb
sãodouble
variáveis, não resultados intermediários, portanto, seus valores são valores de precisão dupla, portanto, são múltiplos de 2 ^ -1074. A subtração desses dois valores de precisão dupla é consequentemente um múltiplo de 2 ^ -1074; portanto, o maior intervalo de expoentes altera a propriedade de que a diferença é 0 se a == b.Você nunca deve comparar carros alegóricos ou duplos pela igualdade; porque, na verdade, você não pode garantir que o número atribuído ao float ou double seja exato.
Para comparar flutuações de igualdade de maneira saudável, é necessário verificar se o valor está "próximo o suficiente" do mesmo valor:
fonte
abs(first - second) < error
(ou<= error
) é mais fácil e conciso.A divisão por zero é indefinida, uma vez que o limite de números positivos tende ao infinito, o limite de números negativos tende ao infinito negativo.
Não tenho certeza se este é C ++ ou Java, pois não há tag de idioma.
fonte
O principal problema é que a representação por computador de um duplo (também conhecido como número flutuante ou número real na linguagem matemática) está errado quando você tem um número decimal "demais", por exemplo, quando você lida com um número duplo que não pode ser escrito como um valor numérico ( pi ou o resultado de 1/3).
Portanto, a == b não pode ser feito com nenhum valor duplo de aeb, como lidar com a == b quando a = 0,333 eb = 1/3? Dependendo do seu sistema operacional vs FPU vs número vs idioma e contagem de 3 após 0, você terá uma verdadeira ou falsa.
De qualquer forma, se você fizer "cálculo de valor duplo" em um computador, precisará lidar com precisão; portanto, em vez de fazê-
a==b
lo, precisará fazê-loabsolute_value(a-b)<epsilon
, e o epsilon é relativo ao que você está modelando naquele momento em seu algoritmo. Você não pode ter um valor epsilon para toda a sua comparação dupla.Em resumo, quando você digita a == b, você tem uma expressão matemática que não pode ser traduzida em um computador (para qualquer número de ponto flutuante).
PS: hum, tudo o que respondo aqui é mais ou menos em outras respostas e comentários.
fonte
Com base na resposta @malarres e no comentário @Taemyr, aqui está minha pequena contribuição:
Meu argumento é o seguinte: a maneira mais fácil de saber se o resultado da divisão é nan ou inf é realmente executar a divisão.
fonte