comparando valores float / double usando o operador ==

A ferramenta de análise de código que eu uso reclama com o abaixo quando eu começo a comparar dois valores float usando o operador de igualdade. Qual é o caminho correto e como fazê-lo? Existe uma function auxiliar (commons- *) que eu possa reutilizar?

Descrição

Não é possível comparar valores de ponto flutuante usando o operador de igual (==)

Explicação

Comparar os valores de ponto flutuante usando os operadores de igualdade (==) ou de desigualdade (! =) Nem sempre é preciso devido a erros de arredondamento.

Recomendação

Compare os dois valores flutuantes para ver se eles estão próximos em valor.

float a; float b; if(a==b) { .. } 

A IBM tem uma recomendação para comparar dois floats, usando divisão em vez de subtração – isso facilita a seleção de um epsilon que funciona para todos os intervalos de input.

 if (abs(a/b - 1) < epsilon) 

Quanto ao valor de epsilon, eu usaria 5.96e-08 como dado nesta tabela da Wikipedia , ou talvez 2x esse valor.

Ele quer que você os compare com a precisão que você precisa. Por exemplo, se você exigir que os primeiros 4 dígitos decimais de seus floats sejam iguais, você usaria:

 if(-0.00001 <= ab && ab <= 0.00001) { .. } 

Ou:

 if(Math.abs(ab) < 0.00001){ ... } 

Onde você adiciona a precisão desejada à diferença dos dois números e compara-a com o dobro da precisão desejada.

Tudo o que você acha que é mais legível. Eu prefiro o primeiro eu mesmo, pois mostra claramente a precisão que você está permitindo em ambos os lados.

a = 5.43421 b = 5.434205 passará a comparação

 private static final float EPSILON = ; if (Math.abs(ab) < EPSILON) ... 

Como o ponto flutuante oferece precisão variável mas incontrolável (ou seja, você não pode definir a precisão diferente de quando você escolhe entre double e float ), você tem que escolher sua própria precisão fixa para comparações.

Note que este não é mais um verdadeiro operador de equivalência, pois não é transitivo. Você pode facilmente obter a igual b e b é igual a c mas não é igual a c .

Edit: note também que se a for negativo e b for um número positivo muito grande, a subtração pode transbordar e o resultado será infinito negativo, mas o teste ainda funcionará, pois o valor absoluto do infinito negativo é infinito positivo, que será ser maior que o EPSILON .

Use commons-lang

 org.apache.commons.lang.math.NumberUtils#compare 

Também commons-math (na sua situação, solução mais apropriada):

 http://commons.apache.org/math/apidocs/org/apache/commons/math/util/MathUtils.html#equals(double, double) 

O tipo float é um valor aproximado – há uma parte expoente e uma parte de valor com precisão finita.
Por exemplo:

 System.out.println((0.6 / 0.2) == 3); // false 

O risco é que um pequeno erro de arredondamento possa tornar uma comparação false , quando matematicamente ela deveria ser true .

A solução alternativa é comparar os carros alegóricos, permitindo que uma pequena diferença ainda seja “igual”:

 static float e = 0.00000000000001f; if (Math.abs(a - b) < e) 

Apache commons-math para o resgate: MathUtils (double x, double y, int maxUlps)

Retorna true se ambos os argumentos forem iguais ou dentro do intervalo de erro permitido (inclusive). Dois números de flutuação são considerados iguais se houver (maxUlps - 1) (ou menos) números de ponto flutuante entre eles, ou seja, dois números de ponto flutuante adjacentes são considerados iguais.

Aqui está o código atual da implementação do Commons Math:

 private static final int SGN_MASK_FLOAT = 0x80000000; public static boolean equals(float x, float y, int maxUlps) { int xInt = Float.floatToIntBits(x); int yInt = Float.floatToIntBits(y); if (xInt < 0) xInt = SGN_MASK_FLOAT - xInt; if (yInt < 0) yInt = SGN_MASK_FLOAT - yInt; final boolean isEqual = Math.abs(xInt - yInt) <= maxUlps; return isEqual && !Float.isNaN(x) && !Float.isNaN(y); } 

Isso fornece o número de flutuantes que podem ser representados entre seus dois valores na escala atual, o que deve funcionar melhor do que um épsilon absoluto.

Fiz uma tentativa com base na maneira como o java implementa == para duplas. Converte para o formato IEEE 754 inteiro e primeiro faz uma comparação bit a bit. Double também fornece o static doubleToLongBits () para obter o formulário inteiro. Usando o bit fiddling você pode ‘arredondar’ a mantissa do double adicionando 1/2 (um bit) e truncando.

De acordo com a observação do supercat, a function primeiro tenta uma simples comparação == e só arredonda se isso falhar. Aqui está o que eu cheguei com alguns comentários (esperançosamente) úteis.

Eu fiz alguns testes limitados, mas não posso dizer que tentei todos os casos de borda. Além disso, não testei o desempenho. Não deveria ser tão ruim.

Acabei de perceber que esta é essencialmente a mesma solução oferecida por Dmitri. Talvez um pouco mais conciso.

 static public boolean nearlyEqual(double lhs, double rhs){ // This rounds to the 6th mantissa bit from the end. So the numbers must have the same sign and exponent and the mantissas (as integers) // need to be within 32 of each other (bottom 5 bits of 52 bits can be different). // To allow 'n' bits of difference create an additive value of 1<<(n-1) and a mask of 0xffffffffffffffffL< 

A modificação a seguir manipula a alteração no caso de sinal em que o valor está em ambos os lados de 0.

 return lhs==rhs?true:((Double.doubleToLongBits(lhs)+0x10L) & 0x7fffffffffffffe0L) == ((Double.doubleToLongBits(rhs)+0x10L) & 0x7fffffffffffffe0L); 

Primeiro, algumas coisas para observar:

  • A maneira “padrão” de fazer isso é escolher um epsilon constante, mas epsilons constantes não funcionam corretamente para todas as faixas numéricas.
  • Se você quiser usar uma constante epsilon sqrt(EPSILON) a raiz quadrada do épsilon de float.h é geralmente considerada um bom valor. (isso vem de um infame “livro laranja” cujo nome me escapa no momento).
  • A divisão de ponto flutuante será lenta, então você provavelmente desejará evitá-la para comparações, mesmo que ela se comporte como um epsilon feito sob medida para as grandezas dos números.

O que você realmente quer fazer? algo assim:
Compare quantos números de ponto flutuante representáveis ​​os valores diferem.

Este código vem desse ótimo artigo de Bruce Dawson. O artigo foi atualizado desde então. A principal diferença é que o artigo antigo quebra a regra de restrição de serrilhado. (lançando pointers flutuantes para o ponteiro int, desreferenciando, escrevendo, lançando de volta). Embora o purista do C / C ++ aponte rapidamente a falha, na prática isso funciona, e considero o código mais legível. No entanto, o novo artigo usa uniões e o C / C ++ consegue manter sua dignidade. Para resumir, dou o código que quebra o aliasing estrito abaixo.

 // Usable AlmostEqual function bool AlmostEqual2sComplement(float A, float B, int maxUlps) { // Make sure maxUlps is non-negative and small enough that the // default NAN won't compare as equal to anything. assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); int aInt = *(int*)&A; // Make aInt lexicographically ordered as a twos-complement int if (aInt < 0) aInt = 0x80000000 - aInt; // Make bInt lexicographically ordered as a twos-complement int int bInt = *(int*)&B; if (bInt < 0) bInt = 0x80000000 - bInt; int intDiff = abs(aInt - bInt); if (intDiff <= maxUlps) return true; return false; } 

A idéia básica no código acima é primeiro notar que, dado o formato de ponto flutuante IEEE 754, {sign-bit, biased-exponent, mantissa} , os números são ordenados lexicograficamente se interpretados como ints de magnitude assinada. Esse é o bit de sinal que se torna o bit de sinal, o e o expoente sempre ultrapassam completamente a mantissa na definição da magnitude do float e porque ele vem em primeiro lugar na determinação da magnitude do número interpretado como um int.

Então, interpretamos a representação de bit do número de ponto flutuante como um int de magnitude assinada. Em seguida, convertemos os ints de magnitude assinada em dois inteiros complementares, subtraindo-os de 0x80000000, se o número for negativo. Então, apenas comparamos os dois valores como se tivéssemos quaisquer ints complementados de dois, e vendo quantos valores eles diferem. Se esse valor for menor que o limite escolhido para quantos floats representáveis ​​os valores podem diferir e ainda serem considerados iguais, você diz que eles são "iguais". Observe que esse método permite que os números "iguais" diferem por valores maiores para foliões de magnitude maior e por valores menores para flutuadores de menor magnitude.