O que há de errado em usar == para comparar carros alegóricos em Java?

177

De acordo com esta página java.sun, == é o operador de comparação de igualdade para números de ponto flutuante em Java.

No entanto, quando eu digito este código:

if(sectionID == currentSectionID)

no meu editor e executar a análise estática, recebo: "JAVA0078 Valores de ponto flutuante comparados com =="

O que há de errado em usar ==para comparar valores de ponto flutuante? Qual é a maneira correta de fazer isso? 

user128807
fonte
29
Como comparar carros alegóricos com == é problemático, não é aconselhável usá-los como IDs; os nomes no seu código de exemplo sugerem que você está fazendo isso; números inteiros longos (longos) são preferidos e o padrão de fato para IDs.
Carl Manaster 06/07/2009
4
Sim, isso foi apenas um exemplo aleatório ou você realmente usa carros alegóricos como IDs? Há uma razão?
Por Wiklander
5
"para campos flutuantes, use o método Float.compare; e para campos duplos, use Double.compare. O tratamento especial de campos flutuantes e duplos é necessário pela existência de Float.NaN, -0.0f e constantes duplas análogas; consulte a documentação Float.equals para obter detalhes. " (Joshua Bloch: Java eficaz)
lbalazscs

Respostas:

211

a maneira correta de testar carros alegóricos para 'igualdade' é:

if(Math.abs(sectionID - currentSectionID) < epsilon)

onde epsilon é um número muito pequeno como 0,00000001, dependendo da precisão desejada.

Vencedor
fonte
26
Consulte o link na resposta aceita ( cygnus-software.com/papers/comparingfloats/comparingfloats.htm ) para saber por que um epsilon fixo nem sempre é uma boa ideia. Especificamente, à medida que os valores nos carros alegóricos sendo comparados aumentam (ou diminuem), o epsilon não é mais apropriado. (Usando epsilon é bom se você conhece seus valores float são todos relativamente razoável, no entanto.)
PT
1
@PT Ele pode multiplicar epsilon com um número e mudar de função if(Math.abs(sectionID - currentSectionID) < epsilon*sectionIDpara resolver esse problema?
enthusiasticgeek
3
Essa pode até ser a melhor resposta até agora, mas ainda é falha. De onde você tira o epsilon?
Michael Piefel
1
@ MichaelPiefel já diz: "dependendo da precisão desejada". Os carros alegóricos, por natureza, são como valores físicos: você só está interessado em um número limitado de posições, dependendo da imprecisão total, quaisquer diferenças além disso são consideradas discutíveis.
precisa saber é o seguinte
Mas o OP realmente queria apenas testar a igualdade e, como se sabe que isso não é confiável, ele precisa usar um método diferente. Ainda assim, eu não entendo que ele saiba qual é a sua "precisão desejada"; portanto, se tudo o que você deseja é um teste de igualdade mais confiável, a questão permanece: de onde você tira o epsilon? Propus usar Math.ulp()minha resposta a esta pergunta.
Michael Piefel 29/08/16
53

Os valores de ponto flutuante podem ser desativados um pouco, portanto, podem não ser exatamente iguais. Por exemplo, definindo um float como "6.1" e imprimindo-o novamente, você pode obter um valor relatado de algo como "6.099999904632568359375". Isso é fundamental para a maneira como os carros alegóricos funcionam; portanto, você não deseja compará-los usando a igualdade, mas sim a comparação dentro de um intervalo, ou seja, se o diff do float com o número com o qual você deseja compará-lo for menor que um determinado valor absoluto.

Este artigo no Registro fornece uma boa visão geral de por que esse é o caso; leitura útil e interessante.

Paul Sonier
fonte
@kevindtimm: para que você faça seus testes de igualdade dessa maneira, se (número == 6.099999904632568359375) sempre que desejar saber o número for igual a 6,1 ... Sim, você está correto ... tudo no computador é estritamente determinístico, apenas que as aproximações usadas para carros alegóricos são contra-intuitivas ao fazer problemas de matemática.
Newtopian
Os valores de ponto flutuante são imprecisos de maneira não determinística em hardware muito específico .
Stuart P. Bentley
1
@Stuart Eu posso estar enganado, mas não acho que o bug do FDIV seja não determinístico. As respostas dadas pelo hardware não respeitar a especificação, mas eram determinística, em que o mesmo cálculo sempre produziu o mesmo resultado incorreto
Gravidade
@ Gravity Você pode argumentar que qualquer comportamento é determinístico, dado um conjunto específico de advertências.
Stuart P. Bentley
Os valores do ponto flutuante não são imprecisos. Todo valor de ponto flutuante é exatamente o que é. O que pode ser impreciso é o resultado de um cálculo de ponto flutuante . Mas cuidado! Quando você vê algo como 0,1 em um programa, esse não é um valor de ponto flutuante. Esse é um literal de ponto flutuante - uma string que o compilador converte em um valor de ponto flutuante fazendo um cálculo .
Solomon Slow
22

Apenas para dar a razão por trás do que todo mundo está dizendo.

A representação binária de um flutuador é meio irritante.

Em binário, a maioria dos programadores conhece a correlação entre 1b = 1d, 10b = 2d, 100b = 4d, 1000b = 8d

Bem, funciona da outra maneira também.

.1b = .5d, .01b = .25d, .001b = .125, ...

O problema é que não há maneira exata de representar a maioria dos números decimais, como .1, .2, .3, etc. Tudo o que você pode fazer é aproximar em binário. O sistema faz um pequeno arredondamento quando os números são impressos para exibir .1 em vez de .10000000000001 ou .999999999999 (que provavelmente estão tão próximos da representação armazenada quanto a .1)

Editar a partir do comentário: a razão pela qual este é um problema são as nossas expectativas. Esperamos totalmente que 2/3 sejam falsificados em algum momento quando o convertermos em decimal, 0,7 ou 0,67 ou 0,6666667 .. Mas não esperamos automaticamente que o valor 1 seja arredondado da mesma maneira que 2/3 --e é exatamente isso que está acontecendo.

A propósito, se você estiver curioso, o número armazenado internamente é uma representação binária pura usando uma "Notação científica" binária. Portanto, se você disser para armazenar o número decimal 10.75d, ele armazenará 1010b para o 10 e 0,11b para o decimal. Assim, ele armazenaria .101011 e, em seguida, salva alguns bits no final para dizer: Mova o ponto decimal quatro casas para a direita.

(Embora tecnicamente não seja mais um ponto decimal, agora é um ponto binário, mas essa terminologia não tornaria as coisas mais compreensíveis para a maioria das pessoas que acharia essa resposta de qualquer utilidade.)

Bill K
fonte
1
@ Mat K - hum, ponto não fixo; se você "salvar alguns bits no final para mover os pontos decimais [N] para a direita", é o ponto flutuante. O ponto fixo assume que a posição do ponto de base é bem fixa. Além disso, em geral, como a mudança do ponto binamal (?) Sempre pode ser feita para deixá-lo com um '1' na posição mais à esquerda, você encontrará alguns sistemas que omitem o primeiro '1', dedicando o espaço assim liberado (1). bit!) para estender o alcance do expoente.
JustJeff
O problema não tem nada a ver com representação binária x decimal. Com o ponto flutuante decimal, você ainda possui itens como (1/3) * 3 == 0.9999999999999999999999999999.
dan04
2
@ dan04 sim, porque 1/3 não possui representação binária OR decimal, ela possui uma representação trinária e seria convertida corretamente dessa maneira :). Os números que listei (.1, .25, etc) têm representações decimais perfeitas, mas não representam binário - e as pessoas estão acostumadas a ter representações "exatas". O BCD lidaria com eles perfeitamente. Essa é a diferença.
Bill K
1
Isso deve ter muito mais votos, pois descreve o problema REAL por trás do problema.
Levite
19

O que há de errado em usar == para comparar valores de ponto flutuante?

Porque não é verdade que 0.1 + 0.2 == 0.3

AakashM
fonte
7
que tal Float.compare(0.1f+0.2f, 0.3f) == 0?
Poder de Aquário
0,1f + 0,2f == 0,3f mas 0,1d + 0,2d! = 0,3d. Por padrão, 0,1 + 0,2 é um duplo. 0,3 também é duplo.
burnabyRails
12

Eu acho que há muita confusão em torno de carros alegóricos (e duplos), é bom esclarecer isso.

  1. Não há nada inerentemente errado no uso de carros alegóricos como IDs na JVM compatível com o padrão [*]. Se você simplesmente definir o ID da bóia como x, não faça nada com ele (ou seja, sem aritmética) e depois teste para y == x, você estará bem. Além disso, não há nada errado em usá-los como chaves em um HashMap. O que você não pode fazer é assumir igualdades x == (x - y) + y, etc. Dito isto, as pessoas geralmente usam tipos inteiros como IDs, e você pode observar que a maioria das pessoas aqui é adiada por esse código; portanto, por razões práticas, é melhor aderir às convenções . Observe que existem tantos doublevalores diferentes quanto longos values, então você não ganha nada usando double. Além disso, a geração do "próximo ID disponível" pode ser complicada com duplas e requer algum conhecimento da aritmética de ponto flutuante. Não vale a pena.

  2. Por outro lado, confiar na igualdade numérica dos resultados de dois cálculos matematicamente equivalentes é arriscado. Isso ocorre devido a erros de arredondamento e perda de precisão ao converter de representação decimal em binária. Isso foi discutido até a morte no SO.

[*] Quando eu disse "JVM em conformidade com os padrões", queria excluir certas implementações de JVM com danos cerebrais. Veja isso .

quant_dev
fonte
Ao usar carros alegóricos como IDs, é preciso ter cuidado para garantir que eles sejam comparados usando em ==vez de equals, ou então garantir que nenhum carro alegórico que se compara de maneira desigual seja armazenado em uma tabela. Caso contrário, um programa que tente, por exemplo, contar quantos resultados únicos podem ser produzidos a partir de uma expressão quando alimentados com várias entradas, pode considerar cada valor de NaN como único.
Super27
O acima mencionado refere-se a Float, não a float.
quant_dev
O que está falando Float? Se alguém tentar criar uma tabela de floatvalores exclusivos e compará-los com ==, as horríveis regras de comparação do IEEE-754 resultarão na inundação da tabela de NaNvalores.
Supercat
floatO tipo não possui equalsmétodo.
quant_dev
Ah - não quis dizer um equalsmétodo de instância, mas o método estático (acho que dentro da Floatclasse) que compara dois valores do tipo float.
Supercat
9

Este é um problema não específico do java. Usar == para comparar dois números flutuantes / duplos / qualquer número decimal pode causar problemas devido à maneira como eles são armazenados. Um flutuador de precisão única (conforme o padrão IEEE 754) possui 32 bits, distribuídos da seguinte forma:

1 bit - Sinal (0 = positivo, 1 = negativo)
8 bits - Expoente (uma representação especial (viés-127) do x em 2 ^ x)
23 bits - Mantisa. O número atual que é armazenado.

A mantisa é o que causa o problema. É como uma notação científica, apenas o número na base 2 (binário) se parece com 1.110011 x 2 ^ 5 ou algo semelhante. Mas, em binário, o primeiro 1 é sempre um 1 (exceto para a representação de 0)

Portanto, para economizar um pouco de espaço na memória (trocadilhos), o IEEE decidiu que o 1 deveria ser assumido. Por exemplo, um mantisa de 1011 é realmente 1.1011.

Isso pode causar alguns problemas na comparação, especialmente com 0, já que 0 não pode ser representado exatamente em um float. Esse é o principal motivo pelo qual o == é desencorajado, além dos problemas matemáticos de ponto flutuante descritos por outras respostas.

Java tem um problema único, pois a linguagem é universal em muitas plataformas diferentes, cada uma das quais poderia ter seu próprio formato de flutuação único. Isso torna ainda mais importante evitar ==.

A maneira correta de comparar dois carros alegóricos (que não são do seu idioma) para a igualdade é a seguinte:

if(ABS(float1 - float2) < ACCEPTABLE_ERROR)
    //they are approximately equal

onde ACCEPTABLE_ERROR é # definido ou alguma outra constante igual a 0,000000001 ou qualquer precisão necessária, como Victor já mencionou.

Alguns idiomas têm essa funcionalidade ou essa constante incorporada, mas geralmente esse é um bom hábito.

CodeFusionMobile
fonte
3
Java tem um comportamento definido para carros alegóricos. Não depende da plataforma.
Yishai 22/01
9

A partir de hoje, a maneira mais rápida e fácil de fazer isso é:

if (Float.compare(sectionID, currentSectionID) == 0) {...}

No entanto, os documentos não especificam claramente o valor da diferença de margem (um epsilon da resposta do @Victor) que está sempre presente nos cálculos em flutuadores, mas deve ser algo razoável, pois faz parte da biblioteca de idiomas padrão.

No entanto, se for necessária uma precisão mais alta ou personalizada,

float epsilon = Float.MIN_NORMAL;  
if(Math.abs(sectionID - currentSectionID) < epsilon){...}

é outra opção de solução.

alisa
fonte
1
Os documentos que você vinculou afirmam "o valor 0 se f1 é numericamente igual a f2", o que faz com que seja o mesmo que fazer, (sectionId == currentSectionId)que não é preciso para pontos flutuantes. o método epsilon é a melhor abordagem, que é nesta resposta: stackoverflow.com/a/1088271/4212710
typoerrpr
8

Os valores do ponto de foating não são confiáveis ​​devido a erro de arredondamento.

Como tal, eles provavelmente não devem ser usados ​​como valores-chave, como sectionID. Use números inteiros ou longse intnão contiver valores possíveis suficientes.

Eric Wilson
fonte
2
Acordado. Dado que esses são IDs, não há razão para complicar as coisas com a aritmética de ponto flutuante.
309 Yohnny
2
Ou um longo. Dependendo de quantos IDs únicos são gerados no futuro, um int pode não ser grande o suficiente.
Wayne Hartman
Quão preciso é o dobro comparado ao float?
Arvindh Mani
1
Os @ArvindhMani doubles são muito mais precisos, mas também são valores de ponto flutuante; portanto, minha resposta foi incluir ambos floate double.
Eric Wilson
7

Além de respostas anteriores, você deve estar ciente de que existem comportamentos estranhos associados -0.0fe +0.0f(eles são ==, mas não equals) e Float.NaNequals, mas não ==) (esperança que eu tenho esse direito - argh, não fazê-lo).

Edit: Vamos verificar!

import static java.lang.Float.NaN;
public class Fl {
    public static void main(String[] args) {
        System.err.println(          -0.0f   ==              0.0f);   // true
        System.err.println(new Float(-0.0f).equals(new Float(0.0f))); // false
        System.err.println(            NaN   ==               NaN);   // false
        System.err.println(new Float(  NaN).equals(new Float( NaN))); // true
    }
} 

Bem-vindo ao IEEE / 754.

Tom Hawtin - linha de orientação
fonte
Se algo for ==, eles serão idênticos até o momento. Como eles podem não ser iguais ()? Talvez você o tenha ao contrário?
Mkb
@ Matt NaN é especial. Double.isNaN (x duplo) em Java é realmente implementado como {return x! = X; } ...
quant_dev 6/07/2009
1
Com flutuadores, ==não significa que os números sejam "idênticos ao bit" (o mesmo número pode ser representado com diferentes padrões de bits, embora apenas um deles seja da forma normalizada). Da mesma forma, -0.0fe 0.0fsão representados por diferentes padrões de bits (o bit de sinal é diferente), mas compare como igual a ==(mas não com equals). Sua suposição de ==comparação bit a bit está, de modo geral, errada.
Pavel Minaev 22/07/2009
5

Você pode usar Float.floatToIntBits ().

Float.floatToIntBits(sectionID) == Float.floatToIntBits(currentSectionID)
aamadmi
fonte
1
Você está no caminho certo. floatToIntBits () é o caminho certo a seguir, mas seria mais fácil usar a função equals () incorporada do Float. Consulte aqui: stackoverflow.com/a/3668105/2066079 . Você pode ver que o padrão equals () utiliza floatToIntBits internamente.
precisa saber é o seguinte
1
Sim se forem objetos de flutuação. Você pode usar a equação acima para primitivas.
aamadmi
4

Primeiro de tudo, eles são float ou float? Se um deles for um float, você deve usar o método equals (). Além disso, provavelmente é melhor usar o método estático Float.compare.

omerkudat
fonte
4

O seguinte usa automaticamente a melhor precisão:

/**
 * Compare to floats for (almost) equality. Will check whether they are
 * at most 5 ULP apart.
 */
public static boolean isFloatingEqual(float v1, float v2) {
    if (v1 == v2)
        return true;
    float absoluteDifference = Math.abs(v1 - v2);
    float maxUlp = Math.max(Math.ulp(v1), Math.ulp(v2));
    return absoluteDifference < 5 * maxUlp;
}

Obviamente, você pode escolher mais ou menos de 5 ULPs ('unidade em último lugar').

Se você estiver na biblioteca do Apache Commons, a Precisionclasse possui compareTo()e equals()com epsilon e ULP.

Michael Piefel
fonte
quando se muda do flutuador para o dobro, este método não funciona como isDoubleEqual (0,1 + 0,2-0,3, 0,0) == falsa
hychou
Parece que você precisa mais de 10_000_000_000_000_000L como o fator para doublecobrir isso.
Michael Piefel 26/09
3

você pode querer que seja ==, mas 123.4444444444443! = 123.4444444444442

KM.
fonte
2

Dois cálculos diferentes que produzem números reais iguais não necessariamente produzem números iguais de ponto flutuante. As pessoas que usam == para comparar os resultados dos cálculos geralmente acabam se surpreendendo com isso, então o aviso ajuda a sinalizar o que poderia ser um bug sutil e difícil de reproduzir.

VoiceOfUnreason
fonte
2

Você está lidando com código terceirizado que usaria flutuadores para itens denominados sectionID e currentSectionID? Apenas curioso.

@ Bill K: "A representação binária de um flutuador é meio irritante." Como assim? Como você faria isso melhor? Existem certos números que não podem ser representados em nenhuma base adequadamente, porque eles nunca terminam. Pi é um bom exemplo. Você só pode aproximar. Se você tiver uma solução melhor, entre em contato com a Intel.

xcramps
fonte
1

Como mencionado em outras respostas, duplos podem ter pequenos desvios. E você pode escrever seu próprio método para compará-los usando um desvio "aceitável". Contudo ...

Há uma classe apache para comparar duplas: org.apache.commons.math3.util.Precision

Ele contém algumas constantes interessantes: SAFE_MINe EPSILON, que são os desvios máximos possíveis de operações aritméticas simples.

Ele também fornece os métodos necessários para comparar, igual ou arredondar o dobro. (usando ulps ou desvio absoluto)

bvdb
fonte
1

Em uma resposta de linha, posso dizer, você deve usar:

Float.floatToIntBits(sectionID) == Float.floatToIntBits(currentSectionID)

Para aprender mais sobre o uso correto de operadores relacionados, estou elaborando alguns casos aqui: Geralmente, existem três maneiras de testar cadeias de caracteres em Java. Você pode usar ==, .equals () ou Objects.equals ().

Como eles são diferentes? == testa a qualidade de referência em strings, significando descobrir se os dois objetos são iguais. Por outro lado, .equals () testa se as duas seqüências têm o mesmo valor logicamente. Por fim, Objects.equals () testa quaisquer nulos nas duas seqüências de caracteres e determina se deve chamar .equals ().

Operador ideal para usar

Bem, isso foi sujeito a muitos debates porque cada um dos três operadores tem seu conjunto único de pontos fortes e fracos. Por exemplo, == geralmente é uma opção preferida ao comparar a referência de objeto, mas há casos em que também parece comparar valores de string.

No entanto, o que você obtém é um valor decrescente porque Java cria uma ilusão de que você está comparando valores, mas, no sentido real, não é. Considere os dois casos abaixo:

Caso 1:

String a="Test";
String b="Test";
if(a==b) ===> true

Caso 2:

String nullString1 = null;
String nullString2 = null;
//evaluates to true
nullString1 == nullString2;
//throws an exception
nullString1.equals(nullString2);

Portanto, é muito melhor usar cada operador ao testar o atributo específico para o qual foi projetado. Mas em quase todos os casos, o Objects.equals () é um operador mais universal, portanto, os desenvolvedores da Web optam por isso.

Aqui você pode obter mais detalhes: http://fluentthemes.com/use-compare-strings-java/

Temas Fluentes
fonte
-2

A maneira correta seria

java.lang.Float.compare(float1, float2)
Eric
fonte
7
Float.compare (float1, float2) retorna um int, portanto ele não pode ser usado em vez de float1 == float2 na condição if. Além disso, ele realmente não resolve o problema subjacente ao qual este aviso está se referindo - que, se floats são resultados de cálculos numéricos, float1! = Float2 pode ocorrer apenas devido a erros de arredondamento.
6286 quant_dev
1
certo, você não pode copiar e colar, você deve verificar o documento primeiro.
Eric
2
O que você pode fazer em vez de float1 == float2 é Float.compare (float1, float2) == 0.
deterb
29
Isso não compra nada - você ainda recebeFloat.compare(1.1 + 2.2, 3.3) != 0
Pavel Minaev 22/07/2009
-2

Uma maneira de reduzir o erro de arredondamento é usar o dobro em vez de flutuar. Isso não fará com que o problema desapareça, mas reduz a quantidade de erros no seu programa e o float quase nunca é a melhor escolha. NA MINHA HUMILDE OPINIÃO.

Peter Lawrey
fonte