Pergunta

Eu estava depurando meu projeto e não consegui encontrar um bug. Finalmente eu localizei. Veja o código. Você acha que está tudo bem, e o resultado será "OK! Ok! Ok!", Não é? Agora compile com VC (eu tentei VS2005 e VS2008).

#include <math.h>
#include <stdio.h>


int main () {
    for ( double x = 90100.0; x<90120.0; x+=1 )
    {
        if ( cos(x) == cos(x) )
            printf ("x==%f  OK!\n", x);
        else
            printf ("x==%f  FAIL!\n", x);
    }

    getchar();
    return 0; 
}

A constante dupla mágica é 90112.0. Quando x <90112.0 está tudo bem, quando x> 90112.0 - não! Você pode mudar porque o pecado.

Alguma ideia? Não se esqueça que o pecado e os cos são periódicos.

Foi útil?

Solução

Pode ser isso: http://www.parashift.com/c++-daq-lite/newbie.html#faq-29.18

Eu sei que é difícil de aceitar, mas a aritmética de ponto flutuante simplesmente não funciona como a maioria das pessoas espera. Pior, algumas das diferenças dependem dos detalhes do hardware de ponto flutuante do seu computador e/ou das configurações de otimização que você usa no seu compilador específico. Você pode não gostar disso, mas é assim. A única maneira de "obtê -lo" é deixar de lado suas suposições sobre como as coisas deveria para se comportar e aceitar as coisas como elas realmente Faz comporte-se...

(com ênfase na palavra "frequentemente"; o comportamento depende do seu hardware, compilador etc.): cálculos e comparações de pontos flutuantes são frequentemente realizados por hardware especial que geralmente contém registros especiais, e esses registros geralmente têm mais bits do que um double. Isso significa que os cálculos intermediários de ponto flutuante geralmente têm mais bits do que sizeof(double), e quando um valor de ponto flutuante é escrito para RAM, geralmente é truncado, muitas vezes perdendo alguns pedaços de precisão ...

Lembre -se disso: as comparações de pontos flutuantes são complicados e sutis e repletos de perigo. Tome cuidado. A maneira como o ponto flutuante na realidade obras é diferente da maneira como a maioria dos programadores tende a pensar deveria trabalhar. Se você pretende usar o ponto flutuante, precisa aprender como ele realmente funciona ...

Outras dicas

Como outros observaram, a biblioteca de matemática do VS está fazendo seu cálculo na FPU X87 e gerando resultados de 80 bits, mesmo que o tipo seja o dobro.

Desta forma:

  1. cos () é chamado e retorna com cos (x) na parte superior da pilha x87 como um flutuador de 80 bits
  2. cos (x) é retirado da pilha x87 e armazenado na memória como um duplo; Isso faz com que ele seja arredondado para flutuação de 64 bits, que altera seu valor
  3. cos () é chamado e retorna com cos (x) na parte superior da pilha x87 como um flutuador de 80 bits
  4. O valor arredondado é carregado na pilha x87 da memória
  5. Os valores arredondados e não arredondados de cos (x) comparam desigual.

Muitas bibliotecas e compiladores de matemática o protegem disso, fazendo o cálculo em flutuação de 64 bits nos registros SSE, quando disponível, ou forçando os valores a serem armazenados e arredondados antes da comparação, ou armazenando e recarregando o resultado final no cálculo real de cos (). A combinação do compilador/biblioteca com quem você está trabalhando não é tão perdoador.

O procedimento cos (x) == cos (x) gerado no modo de liberação:

00DB101A  call        _CIcos (0DB1870h) 
00DB101F  fld         st(0) 
00DB1021  fucompp 

O valor é calculado uma vez e depois clonado, depois comparado consigo mesmo - o resultado ficará ok

O mesmo no modo de depuração:

00A51405  sub         esp,8 
00A51408  fld         qword ptr [x] 
00A5140B  fstp        qword ptr [esp] 
00A5140E  call        @ILT+270(_cos) (0A51113h) 
00A51413  fld         qword ptr [x] 
00A51416  fstp        qword ptr [esp] 
00A51419  fstp        qword ptr [ebp-0D8h] 
00A5141F  call        @ILT+270(_cos) (0A51113h) 
00A51424  add         esp,8 
00A51427  fld         qword ptr [ebp-0D8h] 
00A5142D  fucompp          

Agora, coisas estranhas acontecem.
1. X é carregado no fstack (x, 0)
2. X é armazenado na pilha normal (truncamento)
3. Cosine é calculado, resultado na pilha de flutuação
4. X é carregado novamente
5. X é armazenado na pilha normal (truncamento, como por enquanto, somos "simétricos")
6. O resultado do 1º cosseno que estava na pilha é armazenado na memória, agora, outro truncamento ocorre para o 1º valor
7. Cosine é calculado, segundo resultado se estiver na estilhatação da flutuação, mas esse valor foi truncado apenas uma vez
8. O 1º valor é carregado no FSTack, mas esse valor foi truncado duas vezes (uma vez antes de calcular o cosseno, uma vez depois)
9. Esses 2 valores são comparados - estamos recebendo erros de arredondamento.

Você deve Nunca não comparar duplas para a igualdade na maioria dos casos. Você pode não conseguir o que espera.

Os registros de ponto flutuante podem ter um tamanho diferente dos valores de memória (nas máquinas Intel atuais, os registros da FPU são de 80 bits vs duplas de 64 bits). Se o compilador estiver gerando código que calcula o primeiro cosseno, armazena o valor na memória, calcula o segundo cosseno e compara o valor na memória daquele no registro, os valores poderão diferir (devido a problemas de arredondamento de 80 a 64 bits) .

Os valores de ponto flutuante são um pouco complicados. Google para comparação de pontos flutuantes.

O compilador pode ter gerado código que acaba comparando um valor duplo de 64 bits com um registro de ponto flutuante interno de 80 bits. Testar valores de ponto flutuante para igualdade são propensos a esses tipos de erros - você quase sempre é melhor fazer uma comparação "difusa" como (Fabs (Val1 - Val2) <epsilon) em vez de (Val1 == Val2).

Incrementar e testar um valor de flutuação como uma variável de controle de loop geralmente é uma ideia muito ruim. Crie um Int LCV separado apenas para o loop, se for necessário.

Nesse caso, é mais fácil:

for ( int i = 90100; i<90120; i+=1 )    {
    if ( cos(i) == cos(i) )
        printf ("i==%d  OK!\n", i);
    else
        printf ("i==%d  FAIL!\n", i);
}

Como fazer o problema? Modificar E se quadra:

if ( (float)cos(x) == (float)cos(x) )
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top