Qual seria a maneira mais eficiente de comparar dois double
ou dois float
valores?
Simplesmente fazer isso não está correto:
bool CompareDoubles1 (double A, double B)
{
return A == B;
}
Mas algo como:
bool CompareDoubles2 (double A, double B)
{
diff = A - B;
return (diff < EPSILON) && (-diff < EPSILON);
}
Parece processar resíduos.
Alguém conhece um comparador de flutuador mais inteligente?
<invoke Knuth>
Otimização prematura é a raiz de todo o mal.</invoke Knuth>
Basta ir com abs (ab) <EPS, como observado acima, é claro e fácil de entender.==
pode ser perfeitamente correto, mas isso depende inteiramente do contexto não fornecido na pergunta. Até que esse contexto seja conhecido,==
ainda permanece o "caminho mais eficiente" .Respostas:
Seja extremamente cuidadoso ao usar qualquer uma das outras sugestões. Tudo depende do contexto.
Passei muito tempo rastreando bugs em um sistema que presumia
a==b
se|a-b|<epsilon
. Os problemas subjacentes foram:A presunção implícita em um algoritmo que se
a==b
eb==c
entãoa==c
.Usando o mesmo epsilon para linhas medidas em polegadas e linhas medidas em mils (0,001 polegadas). Isso é
a==b
mas1000a!=1000b
. (É por isso que o AlmostEqual2sComplement solicita o epsilon ou max ULPS).O uso do mesmo epsilon para o cosseno dos ângulos e o comprimento das linhas!
Usando essa função de comparação para classificar itens em uma coleção. (Nesse caso, usar o operador C ++ interno == para duplas produziu resultados corretos.)
Como eu disse: tudo depende do contexto e do tamanho esperado de
a
eb
.BTW,
std::numeric_limits<double>::epsilon()
é a "máquina epsilon". É a diferença entre 1,0 e o próximo valor representável por um duplo. Acho que poderia ser usado na função de comparação, mas apenas se os valores esperados forem menores que 1. (Isso é uma resposta à resposta do @ cdv ...)Além disso, se você basicamente possui
int
aritméticadoubles
(aqui usamos duplos para armazenar valores int em certos casos), sua aritmética estará correta. Por exemplo, 4.0 / 2.0 será o mesmo que 1.0 + 1.0. Isso é contanto que você não faça coisas que resultem em frações (4.0 / 3.0) ou não saia do tamanho de um int.fonte
fabs(a)+fabs(b)
mas compensando NaN, soma 0 e excesso, isso fica bastante complexo.float
/double
é MANTISSA x 2 ^ EXP .epsilon
será dependente do expoente. Por exemplo, se a mantissa tiver 24 bits e o expoente tiver 8 bits, então1/(2^24)*2^127
ou~2^103
é umepsilon
para alguns valores; ou isso se refere a um epsilon mínimo ?|a-b|<epsilon
, não está correto. Por favor, adicione este link à sua resposta; se você concorda em cygnus-software.com/papers/comparingfloats/comparingfloats.htm e eu posso remover meus comentários idiotas.A comparação com um valor epsilon é o que a maioria das pessoas faz (mesmo em programação de jogos).
Você deve alterar sua implementação um pouco:
Edit: Christer adicionou uma pilha de ótimas informações sobre este tópico em uma postagem recente no blog . Aproveitar.
fonte
float a = 3.4; if(a == 3.4){...}
ou seja, quando você está comparando um ponto flutuante armazenado com um literal | Nesse caso, os dois números são armazenados e, portanto, terão a mesma representação, se iguais. Qual é o mal em fazera == b
?EPSILON
for definido comoDBL_EPSILON
. Normalmente, será um valor específico escolhido, dependendo da precisão exigida da comparação.EPSILON
A comparação não funciona quando os carros alegóricos são grandes, pois a diferença entre carros alegóricos consecutivos também se torna grande. Veja este artigo .EPSILON
é praticamente inútil. Você precisa comparar com um limite que faça sentido para as unidades disponíveis. Além disso, use,std::abs
pois está sobrecarregado para diferentes tipos de ponto flutuante.Descobri que o Google C ++ Testing Framework contém uma boa implementação baseada em modelo de plataforma cruzada de AlmostEqual2sComplement, que funciona tanto em duplos quanto em flutuadores. Como ele é liberado sob a licença BSD, usá-lo em seu próprio código não deve ser problema, desde que você mantenha a licença. Extraí o código abaixo de
http://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.hhttps://github.com/google/googletest/blob /master/googletest/include/gtest/internal/gtest-internal.h e adicionou a licença na parte superior.#Define GTEST_OS_WINDOWS com algum valor (ou altere o código em que é usado para algo que se adapta à sua base de código - afinal, é licenciado em BSD).
Exemplo de uso:
Aqui está o código:
EDIT: Este post tem 4 anos. Provavelmente ainda é válido, e o código é bom, mas algumas pessoas encontraram melhorias. É melhor obter a versão mais recente do
AlmostEquals
código-fonte do Teste do Google, e não a que colei aqui.fonte
A comparação de números de ponto flutuante depende do contexto. Como mesmo alterar a ordem das operações pode produzir resultados diferentes, é importante saber o quão "igual" você deseja que os números sejam.
A comparação de números de ponto flutuante de Bruce Dawson é um bom ponto de partida para a comparação de pontos flutuantes.
As seguintes definições são de A arte da programação de computadores de Knuth :
Obviamente, a escolha de epsilon depende do contexto e determina o quanto você deseja que os números sejam iguais.
Outro método de comparar números de ponto flutuante é examinar o ULP (unidades em último lugar) dos números. Embora não lide especificamente com comparações, o artigo O que todo cientista da computação deve saber sobre números de ponto flutuante é um bom recurso para entender como o ponto flutuante funciona e quais são as armadilhas, incluindo o que é ULP.
fonte
fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
salvou minha vida. LOL Observe que esta versão (eu não verifiquei se também se aplica às outras) também considera a alteração que pode ocorrer na parte integrante do número do ponto flutuante (exemplo:2147352577.9999997616 == 2147352576.0000000000
onde você pode ver claramente que há quase uma diferença de2
entre os dois números), o que é bastante agradável! Isso acontece quando o erro de arredondamento acumulado excede a parte decimal do número.std::max(std::abs(a), std::abs(b))
(ou comstd::min()
);std::abs
no C ++ é sobrecarregado com tipos float e double, então funciona muito bem (você sempre pode manterfabs
a legibilidade).Para uma abordagem mais aprofundada, leia Comparando números de ponto flutuante . Aqui está o trecho de código desse link:
fonte
*(int*)&A;
" Irá violar a regra estrita de aliasing?Percebendo que este é um tópico antigo, mas este artigo é um dos mais diretos que encontrei na comparação de números de ponto flutuante e, se você quiser explorar mais, ele também tem referências mais detalhadas e o site principal cobre uma gama completa de problemas Lidar com números de ponto flutuante O Guia de ponto flutuante: Comparação .
Podemos encontrar um artigo um pouco mais prático nas tolerâncias de ponto flutuante revisitadas e observa que existe um teste de tolerância absoluta , que se resume a isso no C ++:
e teste de tolerância relativa :
As notas do artigo que o teste absoluta falha quando
x
ey
são grandes e não no caso relativo quando eles são pequenos. Supondo que a tolerância absoluta e relativa seja a mesma, um teste combinado seria assim:fonte
A maneira portátil de obter epsilon em C ++ é
Então a função de comparação se torna
fonte
Acabei passando bastante tempo analisando material nesse ótimo tópico. Duvido que todo mundo queira gastar tanto tempo, para destacar o resumo do que aprendi e a solução que implementei.
Resumo Rápido
numeric_limits::epsilon()
é a mesma que FLT_EPSILON em float.h. No entanto, isso é problemático porque o epsilon para comparar valores como 1,0 não é o mesmo que o epsilon para valores como 1E9. O FLT_EPSILON está definido para 1.0.fabs(a-b) <= epsilon
no entanto, isso não funciona, porque o epsilon padrão está definido para 1.0. Precisamos escalar epsilon para cima ou para baixo nos termos de a e b.max(a,b)
ou pode obter os próximos números representáveis em torno de a e depois ver se b cai nesse intervalo. O primeiro é chamado método "relativo" e mais tarde é chamado método ULP.Implementação de funções de utilitário (C ++ 11)
fonte
isDefinitelyLessThan
chequesdiff < tolerance
, o que significa que aeb são quase iguais (e, portanto, a não é definitivamente menor que b). Não faz mais sentido verificar a tolerância de diferenças nos dois casos? Ou talvez adicione umorEqualTo
argumento que controla se a verificação aproximada da igualdade deve retornar verdadeira ou não.O código que você escreveu está com erros:
O código correto seria:
(... e sim, isso é diferente)
Gostaria de saber se fabs não faria você perder a avaliação preguiçosa em algum caso. Eu diria que depende do compilador. Você pode tentar os dois. Se eles são equivalentes em média, leve a implementação com fabs.
Se você tiver alguma informação sobre qual dos dois flutuadores tem maior probabilidade de ser maior que os outros, você pode jogar na ordem da comparação para aproveitar melhor a avaliação lenta.
Finalmente, você pode obter melhores resultados inserindo esta função. Não é provável que melhore muito ...
Edit: JO, obrigado por corrigir seu código. Eu apaguei meu comentário de acordo
fonte
Isso é bom se:
Mas, caso contrário, isso o levará a problemas. Os números de precisão dupla têm uma resolução de cerca de 16 casas decimais. Se os dois números que você está comparando são maiores em magnitude que o EPSILON * 1.0E16, então você pode estar dizendo:
Examinarei uma abordagem diferente, que pressupõe que você precisa se preocupar com o primeiro problema e suponha que o segundo seja bom para o seu aplicativo. Uma solução seria algo como:
Isso é caro computacionalmente, mas às vezes é o que é necessário. É isso que temos que fazer na minha empresa, porque lidamos com uma biblioteca de engenharia e as entradas podem variar em algumas dezenas de ordens de magnitude.
De qualquer forma, o ponto é este (e se aplica a praticamente todos os problemas de programação): avalie quais são suas necessidades e, em seguida, encontre uma solução para atender às suas necessidades - não pense que a resposta fácil atenderá às suas necessidades. Se após sua avaliação você achar que isso
fabs(a-b) < EPSILON
é suficiente, perfeito - use-o! Mas esteja ciente de suas deficiências e de outras soluções possíveis também.fonte
Como outros já apontaram, o uso de um epsilon de expoente fixo (como 0,0000001) será inútil para valores distantes do valor do epsilon. Por exemplo, se seus dois valores são 10000.000977 e 10000, então NÃO existem valores de ponto flutuante de 32 bits entre esses dois números - 10000 e 10000.000977 estão tão próximos quanto você pode obter sem serem idênticos bit a bit. Aqui, um epsilon inferior a 0,0009 não tem sentido; você também pode usar o operador de igualdade direta.
Da mesma forma, à medida que os dois valores se aproximam do tamanho epsilon, o erro relativo aumenta para 100%.
Portanto, tentar misturar um número de ponto fixo, como 0,00001, com valores de ponto flutuante (onde o expoente é arbitrário) é um exercício inútil. Isso só funcionará se você puder ter certeza de que os valores do operando estão em um domínio estreito (ou seja, próximo a algum expoente específico) e se você selecionar corretamente um valor épsilon para esse teste específico. Se você puxar um número do ar ("Ei! 0.00001 é pequeno, então isso deve ser bom!"), Você estará fadado a erros numéricos. Passei muito tempo depurando códigos numéricos ruins, em que algum pobre schmuck lança valores aleatórios de epsilon para fazer com que outro caso de teste funcione.
Se você faz programação numérica de qualquer tipo e acredita que precisa buscar epsilons de ponto fixo, LEIA O ARTIGO DE BRUCE SOBRE COMPARAÇÃO DE NÚMEROS DE PONTOS FLUTUANTES .
Comparando números de ponto flutuante
fonte
O Qt implementa duas funções, talvez você possa aprender com elas:
E você pode precisar das seguintes funções, pois
fonte
A comparação de uso geral de números de ponto flutuante geralmente não faz sentido. Como comparar realmente depende de um problema em questão. Em muitos problemas, os números são suficientemente discretizados para permitir compará-los dentro de uma determinada tolerância. Infelizmente, existem tantos problemas em que esse truque realmente não funciona. Por exemplo, considere trabalhar com uma função Heaviside (etapa) de um número em questão (opções de ações digitais vêm à mente) quando suas observações estiverem muito próximas da barreira. Realizar comparações baseadas em tolerância não faria muito bem, pois efetivamente mudaria o problema da barreira original para duas novas. Novamente, não há uma solução de uso geral para esses problemas e a solução específica pode exigir uma alteração no método numérico para obter estabilidade.
fonte
Infelizmente, mesmo o seu código "desperdiçador" está incorreto. EPSILON é o menor valor que pode ser adicionado a 1,0 e alterar seu valor. O valor 1.0 é muito importante - números maiores não mudam quando adicionados ao EPSILON. Agora, você pode dimensionar esse valor para os números que você está comparando para saber se são diferentes ou não. A expressão correta para comparar duas duplas é:
Isso é no mínimo. Em geral, porém, você deve considerar o ruído em seus cálculos e ignorar alguns dos bits menos significativos; portanto, uma comparação mais realista seria semelhante a:
Se o desempenho da comparação for muito importante para você e você souber o intervalo de seus valores, use números de ponto fixo.
fonte
EPSILON
na pergunta éDBL_EPSILON
ouFLT_EPSILON
? O problema está em sua própria imaginação, onde você substituiuDBL_EPSILON
(o que de fato seria a escolha errada) no código que não o usou.Minha turma com base nas respostas postadas anteriormente. Muito parecido com o código do Google, mas eu uso um viés que empurra todos os valores de NaN acima de 0xFF000000. Isso permite uma verificação mais rápida do NaN.
Este código pretende demonstrar o conceito, não ser uma solução geral. O código do Google já mostra como calcular todos os valores específicos da plataforma e eu não queria duplicar tudo isso. Eu fiz testes limitados nesse código.
fonte
Aqui está a prova de que o uso
std::numeric_limits::epsilon()
não é a resposta - ele falha em valores maiores que um:Prova do meu comentário acima:
A execução produz esta saída:
Observe que, no segundo caso (um e apenas maior que um), os dois valores de entrada estão o mais próximo possível e ainda comparam como não estão próximos. Portanto, para valores maiores que 1,0, você também pode usar apenas um teste de igualdade. Épsilons fixos não o salvarão ao comparar valores de ponto flutuante.
fonte
return *(reinterpret_cast<double*>(&x));
embora geralmente funcione, é de fato um comportamento indefinido.numeric_limits<>::epsilon
e o ponto de piso IEEE 754.Encontrei outra implementação interessante em: https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon
fonte
Eu ficaria muito cauteloso com qualquer uma dessas respostas que envolva subtração de ponto flutuante (por exemplo, fabs (ab) <epsilon). Primeiro, os números de ponto flutuante tornam-se mais esparsos em magnitudes maiores e em magnitudes altas o suficiente, onde o espaçamento é maior que epsilon. Segundo, subtrair dois números de ponto flutuante muito próximos (como esses tenderão a ser, considerando que você está procurando uma igualdade quase) é exatamente como você obtém o cancelamento catastrófico .
Embora não seja portátil, acho que a resposta do grom faz o melhor trabalho para evitar esses problemas.
fonte
a
eb
eles mesmos. Não há absolutamente nenhum problema com o uso de subtração de ponto flutuante como parte de uma comparação difusa (embora, como já foi dito, um valor de epsilon absoluta pode ou não ser apropriado para um determinado caso de uso.)Na verdade, existem casos no software numérico em que você deseja verificar se dois números de ponto flutuante são exatamente iguais. Postei isso em uma pergunta semelhante
https://stackoverflow.com/a/10973098/1447411
Portanto, você não pode dizer que "CompareDoubles1" está errado em geral.
fonte
Depende de quão precisa você deseja que a comparação seja. Se você deseja comparar exatamente o mesmo número, basta ir com ==. (Você quase nunca deseja fazer isso, a menos que realmente deseje exatamente o mesmo número.) Em qualquer plataforma decente, você também pode fazer o seguinte:
como
fabs
tende a ser bem rápido. Com bastante rapidez, quero dizer que é basicamente um AND bit a bit, então é melhor que seja rápido.E truques inteiros para comparar duplas e flutuantes são bons, mas tendem a dificultar o manuseio eficaz dos vários pipelines da CPU. E definitivamente não é mais rápido em certas arquiteturas em ordem atualmente devido ao uso da pilha como uma área de armazenamento temporário para valores que estão sendo usados com freqüência. (Carregar-bater-armazenar para aqueles que se importam.)
fonte
Em termos da escala de quantidades:
Se
epsilon
é a pequena fração da magnitude da quantidade (ou seja, valor relativo) em certo sentido físico e /A
eB
tipos é comparável no mesmo sentido, do que eu penso, que o seguinte é bastante correto:fonte
Eu uso este código:
fonte
epsilon
é para isso que serve.epsilon
é apenas a distância entre 1 e o próximo número representável após 1. Na melhor das hipóteses, esse código está apenas tentando verificar se os dois números são exatamente iguais um ao outro, mas, como os não-poderes de 2 estão sendo multiplicados porepsilon
, nem está fazendo isso corretamente.std::fabs(std::min(v1, v2))
está incorreto - para entradas negativas, escolhe aquela com maior magnitude.Eu escrevo isso para java, mas talvez você ache útil. Ele usa longos em vez de duplos, mas cuida de NaNs, subnormais, etc.
Lembre-se de que, após várias operações de ponto flutuante, o número pode ser muito diferente do que esperamos. Não há código para corrigir isso.
fonte
Que tal agora?
Eu já vi várias abordagens - mas nunca vi isso, por isso estou curioso para ouvir comentários também!
fonte
Usei essa função para meu pequeno projeto e ele funciona, mas observe o seguinte:
Erro de precisão dupla pode criar uma surpresa para você. Digamos que epsilon = 1.0e-6, então 1.0 e 1.000001 NÃO devam ser considerados iguais de acordo com o código acima, mas na minha máquina a função os considera iguais, isso ocorre porque 1.000001 não pode ser traduzido com precisão para um formato binário, provavelmente é 1.0000009xxx. Eu o testo com 1.0 e 1.0000011 e desta vez obtenho o resultado esperado.
fonte
Esta é outra solução com o lambda:
fonte
Meu caminho pode não estar correto, mas útil
Converta ambas as flutuações em seqüências de caracteres e faça comparações de seqüências
a sobreposição do operador também pode ser feita
fonte
Você não pode comparar dois
double
com um fixoEPSILON
. Dependendo do valor dedouble
,EPSILON
varia.Uma comparação dupla melhor seria:
fonte
De uma maneira mais genérica:
fonte
a
eb
já forem menores doepsilon()
que a diferença ainda puderem ser significativos. Por outro lado, se os números forem muito grandes, mesmo alguns bits de erro farão com que a comparação falhe, mesmo que você queira que os números sejam considerados iguais. Essa resposta é exatamente o tipo de algoritmo de comparação "genérico" que você deseja evitar.Por que não executar o XOR bit a bit? Dois números de ponto flutuante são iguais se os bits correspondentes forem iguais. Acho que a decisão de colocar os bits do expoente antes da mantissa foi tomada para acelerar a comparação de dois carros alegóricos. Eu acho que muitas respostas aqui estão perdendo o objetivo da comparação epsilon. O valor de epsilon depende apenas de quais números de precisão de ponto flutuante são comparados. Por exemplo, depois de fazer uma aritmética com flutuadores, você obtém dois números: 2.5642943554342 e 2.5642943554345. Eles não são iguais, mas, para a solução, apenas 3 dígitos decimais são importantes; portanto, são iguais: 2.564 e 2.564. Nesse caso, você escolhe epsilon igual a 0,001. A comparação épsilon também é possível com o XOR bit a bit. Corrija-me se eu estiver enganado.
fonte