Evitando Divisão por Zero Usando Comparação Flutuante

5

Analisadores de código-fonte (como o SonarQube) reclamam sobre comparações de igualdade flutuantes (ou duplas) porque a igualdade é uma coisa complicada com floats; os valores comparados podem ser os resultados de cálculos que geralmente têm efeitos de arredondamento de minutos, de modo que 0.3 - 0.2 == 0.1 geralmente retorna falso, enquanto matematicamente ele deve sempre retornar verdadeiro (como testado com o Python 2.7). Portanto, essa reclamação faz todo o sentido para alertar sobre códigos potencialmente perigosos.

Uma abordagem típica para tais situações é verificar uma margem, um épsilon, que deve compensar todos os efeitos de arredondamento, e. g.

if abs(a - b) < epsilon then …

Por outro lado, muitas vezes é possível ver um código que evita um problema de divisão por zero, verificando se o divisor tem igualdade com zero antes que a divisão ocorra:

if divisor == 0.0 then
    // do some special handling like skipping the list element,
    // return 0.0 or whatever seems appropriate, depending on context
else
    result = divident / divisor
endif

Isso parece lidar com o problema div-by-zero, mas não é compatível com o analisador de código-fonte que ainda reclama do ponto divisor == 0.0 . À primeira vista, parece um problema com o analisador. Parece um falso positivo. Verificações de igualdade de flutuação para 0.0 devem ser permitidas, não deveriam?

Após algumas considerações, pensei no caso em que o divisor foi o resultado de uma computação que deveria ter resultado em 0.0 (como 0.3 - 0.2 - 0.1 ) e que agora era algo na faixa de 1e-17 ou 0.00000000000000001 .

Existem duas abordagens para isso agora:

  1. O valor não é exatamente 0.0, portanto a divisão pode ocorrer, o valor resultante será um número de ponto flutuante "normal" (provavelmente; considere 1e200 / 1e-200 que é inf ). Deixe acontecer, o chamador tem que cuidar dos resultados.

ou

  1. O valor deveria ter sido 0.0, logicamente é neste caso, o computador simplesmente não o percebe, então qualquer manipulação especial do caso zero foi planejada deve acontecer aqui também.

Se votarmos na segunda opção, podemos usar a abordagem epsilon e ficar bem. Mas isso trataria valores não-zero verdadeiros, que são muito pequenos, como valores zero-ish. Não temos como distinguir os dois casos.

Isso leva à próxima consideração se um valor verdadeiro diferente de zero, que é muito próximo de 0.0, deve ser dividido por ou se deve ser tratado como o caso zero (isto é, receber o valor especial). manipulação). Afinal, dividir por um valor tão pequeno resultará em valores muito grandes , que muitas vezes serão problemáticos (em gráficos ou similares). Isso certamente está no contexto e não pode ser respondido em geral.

Eu também considerei se a existência de valores zero (-ish) na entrada talvez não fosse a raiz do problema, mas apenas um efeito em si, i. e. talvez a raiz do problema seja mais profunda: talvez um algoritmo que espere um float e que seja dividido por ele nunca deva receber valores que possam se tornar zero (-ish) em primeiro lugar.

Eu posso pensar em casos de uso com números inteiros onde um pode precisar verificar se eles são zero antes de dividir (por exemplo, um índice cuja diferença para um índice de referência é usado como divisor, quando ambos se tornam o mesmo em alguma iteração, a diferença é 0 ), mas não consegui pensar em um bom exemplo em que um valor float poderia se tornar zero-ish. Talvez, se isso acontecesse, fosse apenas um erro lógico?

Então, agora minhas perguntas são:

  1. Existe uma teoria sobre o tópico das verificações de flutuação-zero para evitar problemas de divisão por zero, abordando minhas considerações? Ainda não encontrei nada na Internet sobre isso.
  2. Alguém pode fornecer um exemplo razoável de um contexto e um algoritmo que supostamente espera valores flutuantes que podem se tornar zero e pelos quais ele deve se dividir? E dependendo desse contexto, qual solução (epsilon, puro == 0.0 -check, talvez uma abordagem diferente) você preferiria lá?
por Alfe 05.02.2016 / 16:39
fonte

4 respostas

4

Eu estou do lado da teoria pessoal de OP de que não é uma prática normal permitir que um programa de computador prossiga com uma operação de divisão por zero, ou apenas executar uma verificação mínima antes da divisão.

A exceção é quando você está implementando algo que é muito geral - uma linguagem de programação (como o MATLAB) onde você (como programador) não conhece o contexto / aplicação / caso de uso / significado físico das operações matemáticas é solicitado a executar. Isso pode ocorrer porque a fórmula que está avaliando é fornecida pelo cliente e você não conhece o caso de uso do cliente dessa fórmula. Nesse caso, você usa uma representação especial como Inf ou NaN como um marcador de posição.

Se, no entanto, a fórmula for fornecida como parte de uma caixa de ferramentas estatística, você deverá ser capaz de fornecer uma explicação quando a situação surgir. Veja o exemplo "média ponderada quando o peso total é zero" abaixo.

Existe uma maneira de "inverter" um teste de underflow de divisor. Matematicamente se b não for zero e abs(a) / abs(b) > abs(c) em que c é o maior valor de ponto flutuante representável, em seguida, abs(a) > abs(c) * abs(b) . No entanto, na prática, requer uma implementação mais cuidadosa do que aquela . Você pode encontrar uma função de biblioteca matemática que permita passar em (a, b) e retornará se a divisão transborda, transborda ou tem pouca precisão.

Os analisadores de código-fonte procuram padrões no código; eles não são sofisticados o suficiente para decidir se a lógica de solução alternativa de alguém é suficiente para o propósito de design do aplicativo. (Na verdade, mesmo o programador médio pode ser desqualificado para tomar essa decisão.) Os analisadores de código-fonte devem ser aumentados com uma pessoa qualificada para tomar essa decisão.

Um denominador de zero pode ocorrer em muitas manipulações matemáticas: fórmulas, séries infinitas (soma de sequência), etc. Existem muitos métodos matemáticos para calcular o resultado apesar de termos denominadores que se aproximam de zero (ie não exatamente zero, mas são menores do que o valor representável por máquina). Isto significa que as fórmulas não devem ser avaliadas textualmente - elas são transformadas usando alguns métodos de cálculo, e para cada fórmula pode haver várias versões alternativas que são escolhidas para evitar a divisão por zero. / p>

Outra situação surge na média ponderada dos dados. Se você executar uma consulta que selecione um subconjunto de dados e quando:

  1. a soma de pesos para o subconjunto de dados é zero ou
  2. quando o subconjunto está realmente vazio, ou seja, a consulta não retorna nenhum resultado

então a maneira correta de expressar essa situação é "amostras insuficientes (dados) para a consulta", etc.

Na trigonometria básica, algumas representações (declive) são muito sensíveis a problemas de divisão, enquanto uma representação alternativa (rolamento, ou seja, ângulo) não seria sensível. Por exemplo, para representar uma linha em um plano 2D, em que linhas verticais e quase verticais precisam ser representadas de forma tão robusta quanto linhas horizontais e quase horizontais, você pode:

  1. alterne entre as linhas que são íngremes e as que não são. Para linhas com mais de 45 graus, você usaria (x / y) em vez de (y / x) como a inclinação "invertida" da linha, para evitar a divisão por números pequenos.
  2. Use uma representação alternativa como a*x + b*y + c == 0 e armazene os parâmetros (a, b, c) com o requisito de que (a^2 + b^2) deve ser igual a 1.0 para o caso normal e 0.0 se a linha for degenerada (não-a-linha).

Vale ressaltar que a degeneração é inevitável em muitos contextos diferentes (e em formas específicas do contexto). Por exemplo, se o usuário passa em uma "linha" from point (x1, y1) to point (x2, y2) e pede para calcular sua inclinação, e acontece que (x1 == x2 and y1 == y2) , então não há inclinação, pois não há linha, pois existe apenas um único ponto na linha entrada do usuário.

    
por 05.02.2016 / 16:56
fonte
2

Pergunta 1) Eu não chamaria isso de 'teoria', mas há muito conhecimento pragmático em computação estatística e científica sobre como lidar com divisão por 0 ou pegar o log de 0. Não existe uma 'teoria' , porque como você lida com isso depende inteiramente do contexto. Às vezes, é totalmente esperado e desejável que uma quantidade computada atinja exatamente 0. Isso pode indicar apenas que sua solução convergiu totalmente. Em outros casos, isso pode significar que você está tentando aplicar um algoritmo a um domínio válido, e você deve reformular o problema.

Pergunta 2) Uma das operações mais comuns na estatística é dividir uma probabilidade por outra para formar uma razão de chances. Ambas as probabilidades podem ser arbitrariamente pequenas. No entanto, alegar que um evento tem uma probabilidade de exatamente 0 é uma afirmação absurdamente strong na maioria das circunstâncias, portanto, quando você estiver configurando seu problema, poderá adicionar um número muito pequeno a todas as probabilidades de entrada. Isso representa o nosso conhecimento "anterior" de que nenhum dos eventos é absolutamente impossível. Isso também tem o efeito de impedir a divisão por 0, ou de levar o log de 0 ao calcular odds-ratios.

    
por 06.02.2016 / 00:08
fonte
1

Eu acho que isso é bastante equivocado. Na maioria das implementações hoje em dia, a divisão por zero lhe dará infinito, que se comporta muito como um número muito, muito grande. Se você tem um quociente que pode se tornar zero, então ele ficará muito próximo de zero, então seu quociente será muito grande, então você tem um problema - que você tem que resolver, se você verificar se há divisão por zero ou não.

Se você calcular uma função como sin (x) / x, onde tanto o numerador quanto o denominador podem se tornar zero simultaneamente, então o teste para x == 0 é absolutamente bom (e obviamente você precisa retornar 1.0 nesse caso, mas se você calcular (2 sin x) / x, você precisa retornar 2.0 Se você calcular x / sin (x) e x está perto de um múltiplo de pi, então checando a divisão por zero exceto quando x = 0 está errado .

    
por 05.02.2016 / 16:58
fonte
0

a) Se a divisão por zero é um problema, então o excesso também é um problema.

b) Se você tiver cheques para se proteger contra overflows, você não precisa de cheques para se proteger contra divisão por zero

c) Você nunca precisa se preocupar com divisão por zero.

d) Fazer if( divisor == 0) antes da divisão é sempre desnecessário ou errado.

    
por 05.02.2016 / 21:55
fonte