Eu sei que usar ==
para verificar a igualdade de variáveis de ponto flutuante não é uma boa maneira. Mas eu só quero saber isso com as seguintes declarações:
float x = ...
float y = x;
assert(y == x)
Desde que y
é copiado x
, a afirmação será verdadeira?
c++
floating-point
Wei Li
fonte
fonte
-m32
) ou instruindo o GCC a usar o x87 FPU (-mfpmath=387
).Respostas:
além da
assert(NaN==NaN);
caso apontado por kmdreko, você pode ter situações com x87-math, quando flutuadores de 80 bits são temporariamente armazenados na memória e, posteriormente, comparados com valores que ainda são armazenados dentro de um registro.Possível exemplo mínimo, que falha com o gcc9.2 quando compilado com
-O2 -m32
:Demonstração Godbolt: https://godbolt.org/z/X-Xt4R
O
volatile
provavelmente pode ser omitido, se você conseguir criar registo pressão suficiente para tery
armazenado e recarregado a partir da memória (mas confundir o suficiente compilador, não omitir a comparação todos juntos).Consulte a referência de perguntas frequentes do GCC:
fonte
float
precisão padrão com precisão extra.-ffloat-store
parece ser o caminho para evitar isso.Não será verdade se
x
forNaN
, pois as comparações sempreNaN
são falsas (sim, atéNaN == NaN
). Para todos os outros casos (valores normais, valores subnormais, infinitos, zeros), essa afirmação será verdadeira.O conselho para evitar
==
flutuações se aplica aos cálculos devido ao fato de os números de ponto flutuante serem incapazes de expressar muitos resultados exatamente quando usados em expressões aritméticas. A atribuição não é um cálculo e não há razão para que a atribuição produza um valor diferente do original.A avaliação de precisão estendida deve ser um problema, se o padrão for seguido. De
<cfloat>
herdado de C [5.2.4.2.2.8] ( ênfase minha ):No entanto, como os comentários apontaram, alguns casos com certos compiladores, opções de construção e destinos podem tornar isso paradoxalmente falso.
fonte
x
é calculado em um registro na primeira linha, mantendo mais precisão do que o mínimo para afloat
. Oy = x
pode estar na memória, mantendo apenasfloat
precisão. Em seguida, o teste de igualdade seria realizado com a memória em relação ao registrador, com diferentes precisões e, portanto, sem garantia.x+pow(b,2)==x+pow(a,3)
pode diferirauto one=x+pow(b,2); auto two=y+pow(a,3); one==two
porque um pode comparar usando mais precisão do que o outro (se um / dois são valores de 64 bits em memória ram, enquanto valores intermediários são 80 bits em fpu). Então, a tarefa pode fazer alguma coisa, às vezes.gcc -ffloat-store
a cumprir estritamente. Mas esta questão é sobrex=y; x==y;
sem fazer nada para qualquer um dos dois. Sey
já estiver arredondado para caber em um flutuador, a conversão em duplo ou longo duplo e voltar não mudará o valor. ...Sim,
y
certamente assumirá o valor dex
:Não há margem de manobra para outros valores a serem atribuídos.
(Outros já apontaram que uma comparação de equivalência
==
será avaliadafalse
para os valores de NaN.)O problema usual do ponto flutuante
==
é que é fácil não ter exatamente o valor que você pensa ter. Aqui, sabemos que os dois valores, sejam eles quais forem, são os mesmos.fonte
[expr]
. Se eu devo ignorar os links e focar nas citações, fico com a confusão de que, por exemplo, C.5.3 parece não abordar o uso do termo "valor" ou do termo "resultado" (embora use "result" uma vez em seu contexto normal em inglês). Talvez você possa descrever mais claramente onde acha que o padrão faz uma distinção e fornecer uma única citação clara para isso acontecer. Obrigado!Sim, em todos os casos (desconsiderando os problemas de NaNs e x87), isso será verdade.
Se você fizer um teste
memcmp
neles, poderá testar a igualdade e comparar NaNs e sNaNs. Isso também exigirá que o compilador leve o endereço da variável que forçará o valor a 32 bits emfloat
vez de 80 bits. Isso eliminará os problemas do x87. A segunda afirmação aqui pretende falhar ao mostrar que==
não comparará os NaNs como verdadeiros:Observe que se os NaNs tiverem uma representação interna diferente (por exemplo, mantissa diferente), o valor
memcmp
não será verdadeiro.fonte
Em casos usuais, seria avaliado como verdadeiro. (ou a declaração de afirmação não fará nada)
Editar :
Por "casos usuais", quero dizer, estou excluindo os cenários acima mencionados (como valores de NaN e unidades de ponto flutuante de 80x87), conforme apontado por outros usuários.
Dada a obsolescência dos chips 8087 no contexto atual, a questão é bastante isolada e, para que a questão seja aplicável no estado atual da arquitetura de ponto flutuante usada, é verdadeira para todos os casos, exceto para NaNs.
(referência sobre 8087 - https://home.deec.uc.pt/~jlobo/tc/artofasm/ch14/ch143.htm )
Parabéns ao @chtz por reproduzir um bom exemplo e ao @kmdreko por mencionar os NaNs - não os conhecia antes!
fonte
x
estar em um registro de ponto flutuante enquantoy
está carregado da memória. A memória pode ter menos precisão que um registro, causando falha na comparação.float
valor sem precisão extra.int a=1; int b=a; assert( a==b );
uma afirmação, acho que faz sentido responder a essa pergunta em relação a um compilador que funcione corretamente (embora note que algumas versões de alguns compiladores possuem / têm conhecido por errar). Em termos práticos, se por algum motivo um compilador não remover a precisão extra do resultado de uma atribuição armazenada em um registro, deve fazê-lo antes de usar esse valor.Sim, ele retornará True sempre, exceto se for NaN . Se o valor da variável for NaN , ele sempre retornará False !
fonte