Primeiro de tudo, os valores de ponto flutuante não são "aleatórios" em seu comportamento. A comparação exata pode e faz sentido em muitos usos no mundo real. Mas se você usar o ponto flutuante, precisará estar ciente de como ele funciona. Errar do lado de assumir que o ponto flutuante funciona como números reais fará com que você codifique rapidamente. Ao assumir que os resultados de ponto flutuante têm um grande número aleatório associado a eles (como a maioria das respostas sugeridas aqui), você obtém um código que parece funcionar no início, mas acaba tendo erros de grande magnitude e cantos quebrados.
Primeiro de tudo, se você deseja programar com ponto flutuante, leia este:
O que todo cientista da computação deve saber sobre aritmética de ponto flutuante
Sim, leia tudo. Se isso for um fardo demais, você deve usar números inteiros / ponto fixo para seus cálculos até ter tempo para lê-lo. :-)
Agora, com isso dito, os maiores problemas com comparações exatas de ponto flutuante se resumem a:
O fato de que muitos valores que você pode escrever na fonte, ou ler com scanf
ou strtod
, não existem como valores de ponto flutuante e se silenciosamente convertidos para a aproximação mais próxima. Era sobre isso que a resposta de demon9733 estava falando.
O fato de muitos resultados serem arredondados devido à falta de precisão suficiente para representar o resultado real. Um exemplo fácil de como você pode ver isso é adicionar x = 0x1fffffe
e y = 1
flutuar. Aqui, x
possui 24 bits de precisão na mantissa (ok) e y
tem apenas 1 bit, mas quando você os adiciona, os bits não estão em locais sobrepostos e o resultado precisaria de 25 bits de precisão. Em vez disso, é arredondado (para 0x2000000
no modo de arredondamento padrão).
O fato de muitos resultados serem arredondados devido à necessidade de infinitos lugares para o valor correto. Isso inclui resultados racionais, como 1/3 (que você conhece do decimal, que ocupa infinitamente muitas casas), mas também 1/10 (que também ocupa infinitamente muitas casas no binário, já que 5 não é uma potência de 2), além de resultados irracionais, como a raiz quadrada de qualquer coisa que não seja um quadrado perfeito.
Arredondamento duplo. Em alguns sistemas (particularmente x86), as expressões de ponto flutuante são avaliadas com maior precisão do que seus tipos nominais. Isso significa que, quando um dos tipos de arredondamento acima ocorrer, você terá duas etapas de arredondamento, primeiro o arredondamento do resultado para o tipo de maior precisão, depois o arredondamento para o tipo final. Como exemplo, considere o que acontece em decimal se você arredondar 1,49 para um número inteiro (1), versus o que acontece se você primeiro arredondar para uma casa decimal (1,5) e depois arredondar o resultado para um número inteiro (2). Essa é realmente uma das áreas mais desagradáveis para lidar com ponto flutuante, já que o comportamento do compilador (especialmente para compiladores de bugs e não conformes como o GCC) é imprevisível.
Funções transcendentes ( trig
, exp
, log
, etc.) não são especificados para ter resultados correctamente arredondados; o resultado é especificado apenas como correto em uma unidade no último local de precisão (geralmente chamado de 1ulp ).
Ao escrever um código de ponto flutuante, lembre-se do que está fazendo com os números que podem causar resultados inexatos e faça comparações de acordo. Muitas vezes, faz sentido comparar com um "epsilon", mas esse epsilon deve ser baseado na magnitude dos números que você está comparando , e não em uma constante absoluta. (Nos casos em que um epsilon constante absoluto funcionaria, isso é fortemente indicativo de que o ponto fixo, e não o ponto flutuante, é a ferramenta certa para o trabalho!)
Edit: Em particular, uma verificação epsilon relativa à magnitude deve ser algo como:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y))
De onde FLT_EPSILON
é a constante float.h
(substitua-a DBL_EPSILON
por double
s ou LDBL_EPSILON
por long double
s) e K
é uma constante que você escolhe para que o erro acumulado de seus cálculos seja definitivamente limitado por K
unidades em último lugar (e se você não tiver certeza de que recebeu o erro cálculo limite certo, façaK
algumas vezes maior do que o que seus cálculos dizem que deveria ser).
Por fim, observe que, se você usar isso, poderá ser necessário algum cuidado especial próximo de zero, pois FLT_EPSILON
não faz sentido para os anormais. Uma solução rápida seria fazê-lo:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y) || fabs(x-y) < FLT_MIN)
e da mesma forma substitua DBL_MIN
se estiver usando duplas.
fabs(x+y)
é problemático sex
ey
(pode) ter sinal diferente. Ainda assim, uma boa resposta contra a maré de comparações entre cultos de carga.x
ey
tiver sinal diferente, não há problema. O lado direito será "muito pequeno", mas comox
ey
com sinais diferentes, eles não devem ser iguais. (A menos que sejam tão pequenos que não sejam anormais, mas o segundo caso seja detectado)Como 0 é exatamente representável como um número de ponto flutuante IEEE754 (ou usando qualquer outra implementação de números fp com os quais já trabalhei), a comparação com 0 é provavelmente segura. Você pode ser mordido, no entanto, se o seu programa computar um valor (como
theView.frame.origin.x
) que você tenha motivos para acreditar que deve ser 0, mas que sua computação não pode garantir que seja 0.Para esclarecer um pouco, um cálculo como:
(a menos que seu idioma ou sistema esteja quebrado) criará um valor tal que (areal == 0.0) retorne verdadeiro, mas outro cálculo, como
não deve.
Se você pode garantir a si mesmo que seus cálculos produzem valores que são 0 (e não apenas que eles produzam valores que devem ser 0), você pode ir em frente e comparar os valores de fp com 0. Se você não pode se assegurar no grau exigido , é melhor seguir a abordagem usual da "igualdade tolerada".
Nos piores casos, a comparação descuidada dos valores de FP pode ser extremamente perigosa: pense em aviônicos, orientação de armas, operações de usinas, navegação de veículos, quase qualquer aplicativo em que a computação encontre o mundo real.
Para Angry Birds, não é tão perigoso.
fonte
1.30 - 2*(0.65)
é um exemplo perfeito de uma expressão que obviamente avalia como 0,0 se o seu compilador implementa a IEEE 754, porque as dobras representadas como0.65
e1.30
têm os mesmos significandos, e a multiplicação por duas é obviamente exata.Eu quero dar uma resposta um pouco diferente das outras. Eles são ótimos para responder à sua pergunta como indicado, mas provavelmente não para o que você precisa saber ou qual é o seu problema real.
Ponto flutuante nos gráficos é bom! Mas quase não há necessidade de comparar os carros alegóricos diretamente. Por que você precisaria fazer isso? Os gráficos usam flutuadores para definir intervalos. E comparar se um flutuador está dentro de um intervalo também definido por flutuadores é sempre bem definido e apenas precisa ser consistente, não exato ou preciso! Contanto que um pixel (que também é um intervalo!) Possa ser atribuído, é tudo o que os gráficos precisam.
Portanto, se você quiser testar se o seu ponto está fora de um intervalo de [0..width [], tudo bem. Apenas certifique-se de definir a inclusão de maneira consistente. Por exemplo, sempre defina inside is (x> = 0 && x <width). O mesmo vale para testes de interseção ou batida.
No entanto, se você estiver abusando de uma coordenada gráfica como algum tipo de sinalizador, como, por exemplo, para ver se uma janela está encaixada ou não, você não deve fazer isso. Use um sinalizador booleano que é separado da camada de apresentação gráfica.
fonte
Comparar com zero pode ser uma operação segura, desde que o zero não seja um valor calculado (conforme observado na resposta acima). A razão para isso é que zero é um número perfeitamente representável em ponto flutuante.
Falando de valores perfeitamente representáveis, você obtém 24 bits de alcance em uma noção de potência de dois (precisão única). Portanto, 1, 2, 4 são perfeitamente representáveis, assim como 0,5, 0,25 e 0,125. Contanto que todos os seus bits importantes estejam em 24 bits, você será dourado. Portanto, 10.625 pode ser representado com precisão.
Isso é ótimo, mas rapidamente desmoronará sob pressão. Dois cenários vêm à mente: 1) Quando um cálculo está envolvido. Não confie nesse sqrt (3) * sqrt (3) == 3. Simplesmente não será assim. E provavelmente não estará dentro de um épsilon, como sugerem algumas das outras respostas. 2) Quando qualquer NPOT (NPOT) estiver envolvido. Portanto, pode parecer estranho, mas 0,1 é uma série infinita em binário e, portanto, qualquer cálculo envolvendo um número como esse será impreciso desde o início.
(Ah, e a pergunta original mencionou comparações com zero. Não se esqueça que -0,0 também é um valor de ponto flutuante perfeitamente válido.)
fonte
[A 'resposta correta' encobre a seleção
K
. A seleçãoK
acaba sendo tão ad-hoc quanto a seleção,VISIBLE_SHIFT
mas a seleçãoK
é menos óbvia, porque, ao contrárioVISIBLE_SHIFT
, não se baseia em nenhuma propriedade de exibição. Assim, escolha seu veneno - selecioneK
ou selecioneVISIBLE_SHIFT
. Esta resposta defende a seleçãoVISIBLE_SHIFT
e, em seguida, demonstra a dificuldade em selecionarK
]Precisamente devido a erros de arredondamento, você não deve usar a comparação de valores 'exatos' para operações lógicas. No seu caso específico de uma posição em uma exibição visual, não importa se a posição é 0,0 ou 0,0000000003 - a diferença é invisível aos olhos. Portanto, sua lógica deve ser algo como:
No entanto, no final, 'invisível aos olhos' dependerá das propriedades da sua exibição. Se você pode limitar o display (você deve conseguir); escolha
VISIBLE_SHIFT
ser uma fração desse limite superior.Agora, a 'resposta certa' se baseia,
K
então vamos explorar a escolhaK
. A 'resposta correta' acima diz:Então nós precisamos
K
. Se conseguirK
é mais difícil, menos intuitivo do que selecionar o meu,VISIBLE_SHIFT
então você decidirá o que funciona para você. Para descobrirK
, vamos escrever um programa de teste que analise váriosK
valores para que possamos ver como ele se comporta. Deveria ser óbvio como escolherK
, se a 'resposta certa' for utilizável. Não?Vamos usar como detalhes da 'resposta certa':
Vamos apenas tentar todos os valores de K:
Ah, então K deve ser 1e16 ou maior se eu quiser que 1e-13 seja 'zero'.
Então, eu diria que você tem duas opções:
K
.fonte
K
que são difíceis e não intuitivas de selecionar.A pergunta correta: como comparar pontos no Cocoa Touch?
A resposta correta: CGPointEqualToPoint ().
Uma pergunta diferente: dois valores calculados são iguais?
A resposta postada aqui: Eles não são.
Como verificar se eles estão próximos? Se você quiser verificar se eles estão próximos, não use CGPointEqualToPoint (). Mas não verifique se eles estão próximos. Faça algo que faça sentido no mundo real, como verificar se um ponto está além de uma linha ou se está dentro de uma esfera.
fonte
A última vez que verifiquei o padrão C, não havia necessidade de operações de ponto flutuante em duplas (total de 64 bits, mantissa de 53 bits) para ter precisão acima dessa precisão. No entanto, algum hardware pode fazer as operações em registradores de maior precisão, e o requisito foi interpretado como significando nenhum requisito para limpar bits de ordem inferior (além da precisão dos números sendo carregados nos registradores). Assim, você pode obter resultados inesperados de comparações como essa, dependendo do que resta nos registros de quem dormiu lá por último.
Dito isto, e apesar dos meus esforços para eliminá-lo sempre que o vejo, o equipamento em que trabalho tem muito código C que é compilado usando gcc e executado no linux, e não notamos nenhum desses resultados inesperados há muito tempo . Não tenho idéia se isso ocorre porque o gcc está limpando os bits de baixa ordem para nós, os registradores de 80 bits não são usados para essas operações em computadores modernos, o padrão foi alterado ou o quê. Gostaria de saber se alguém pode citar capítulos e versículos.
fonte
Você pode usar esse código para comparar float com zero:
Isso será comparado com precisão de 0,1, o suficiente para o CGFloat nesse caso.
fonte
int
sem segurotheView.frame.origin.x
está dentro / próximo do intervalo deint
leads que leva ao comportamento indefinido (UB) - ou, nesse caso, 1/100 do intervalo deint
.}
fonte
Estou usando a seguinte função de comparação para comparar um número de casas decimais:
fonte
Eu diria que a coisa certa é declarar cada número como um objeto e depois definir três coisas nesse objeto: 1) um operador de igualdade. 2) um método setAcceptableDifference. 3) o próprio valor. O operador de igualdade retornará true se a diferença absoluta de dois valores for menor que o valor definido como aceitável.
Você pode subclassificar o objeto para se adequar ao problema. Por exemplo, barras redondas de metal entre 1 e 2 polegadas podem ser consideradas de diâmetro igual se seus diâmetros diferirem em menos de 0,0001 polegadas. Então você chamaria setAcceptableDifference com o parâmetro 0.0001 e usaria o operador de igualdade com confiança.
fonte