bug precisão gcc?

votos
8

Só posso supor que este é um bug. O primeiro afirmar passa enquanto a segunda falha:

double sum_1 =  4.0 + 6.3;
assert(sum_1 == 4.0 + 6.3);

double t1 = 4.0, t2 = 6.3;

double sum_2 =  t1 + t2;
assert(sum_2 == t1 + t2);

Se não for um bug, por quê?

Publicado 27/08/2009 em 00:03
fonte usuário
Em outras línguas...                            


7 respostas

votos
13

Você está comparando números de ponto flutuante. Não faça isso, números de ponto flutuante tem erro de precisão inerente em algumas circunstâncias. Em vez disso, tomar o valor absoluto da diferença dos dois valores e afirmar que o valor é inferior a um número pequeno (epsilon).

void CompareFloats( double d1, double d2, double epsilon )
{
    assert( abs( d1 - d2 ) < epsilon );
} 

Isto não tem nada a ver com o compilador e tudo a ver com a maneira como números de ponto flutuante são implementadas. aqui é a especificação IEEE:

http://www.eecs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF

Respondeu 27/08/2009 em 00:06
fonte usuário

votos
12

Isso é algo que tem me mordido, também.

Sim, números de ponto flutuante nunca deve ser comparado por igualdade por causa do erro de arredondamento, e você provavelmente sabia disso.

Mas neste caso, você está computando t1+t2, em seguida, calcular-lo novamente. Certamente que tem de produzir um resultado idêntico?

Aqui está o que provavelmente está acontecendo. Aposto que você está executando isso em uma CPU x86, correto? A FPU x86 usa 80 bits para seus registros internos, mas os valores na memória são armazenados como duplos de 64 bits.

Então t1+t2é calculado em primeiro lugar com 80 bits de precisão, então - presumo - armazenado para memória sum_2com 64 bits de precisão - e alguns arredondamento ocorre. Para o assert, ele é carregado de volta em um registrador de ponto flutuante, e t1+t2é calculado novamente, novamente com 80 bits de precisão. Então agora você está comparando sum_2, que foi previamente arredondado para um valor de ponto flutuante de 64 bits, com t1+t2, que foi calculado com maior precisão (80 bits) - e é por isso que os valores não são exatamente idênticos.

Editar Então, por que passar o primeiro teste? Neste caso, o compilador provavelmente avalia 4.0+6.3em tempo de compilação e armazena-lo como uma quantidade de 64 bits - tanto para a atribuição e para o assert. Assim, os valores idênticos estão a ser comparado, e a afirmar passa.

Segundo Editar Aqui está o código assembly gerado para a segunda parte do código (gcc, x86), com comentários - praticamente segue o cenário descrito acima:

// t1 = 4.0
fldl    LC3
fstpl   -16(%ebp)

// t2 = 6.3
fldl    LC4
fstpl   -24(%ebp)

// sum_2 =  t1+t2
fldl    -16(%ebp)
faddl   -24(%ebp)
fstpl   -32(%ebp)

// Compute t1+t2 again
fldl    -16(%ebp)
faddl   -24(%ebp)

// Load sum_2 from memory and compare
fldl    -32(%ebp)
fxch    %st(1)
fucompp

Nota interessante: Esta foi compilado sem otimização. Quando ele é compilado com -O3o compilador otimiza todos do código de distância.

Respondeu 27/08/2009 em 00:16
fonte usuário

votos
3

Ao comparar números de ponto flutuante de proximidade normalmente você quer medir sua diferença relativa, que é definido como

if (abs(x) != 0 || abs(y) != 0) 
   rel_diff (x, y) = abs((x - y) / max(abs(x),abs(y))
else
   rel_diff(x,y) = max(abs(x),abs(y))

Por exemplo,

rel_diff(1.12345, 1.12367)   = 0.000195787019
rel_diff(112345.0, 112367.0) = 0.000195787019
rel_diff(112345E100, 112367E100) = 0.000195787019

A idéia é medir o número de dígitos iniciais significativos os números têm em comum; se você tomar o -log10 de 0,000195787019 você começa 3,70821611, que é sobre o número de base de líder 10 dígitos todos os exemplos têm em comum.

Se você precisa para determinar se dois números de ponto flutuante são iguais você deve fazer algo como

if (rel_diff(x,y) < error_factor * machine_epsilon()) then
  print "equal\n";

onde epsilon máquina é o menor número que pode ser realizado na mantissa do hardware de ponto flutuante a ser utilizado. A maioria das linguagens de computador têm uma função chamada para obter esse valor. error_factor deve basear-se no número de dígitos significativos que você acha que vai ser consumido por erros de arredondamento (e outros) nos cálculos dos números x e y. Por exemplo, se eu soubesse que x e y foram o resultado de cerca de 1000 somatórios e não sabia quaisquer limites sobre os números a serem somados, gostaria de definir error_factor a cerca de 100.

Tentou adicionar-las como links, mas não uma vez que este é meu primeiro post poderia:

  • en.wikipedia.org/wiki/Relative_difference
  • en.wikipedia.org/wiki/Machine_epsilon
  • en.wikipedia.org/wiki/Significand (mantissa)
  • en.wikipedia.org/wiki/Rounding_error
Respondeu 27/08/2009 em 03:09
fonte usuário

votos
3

Eu duplicado o seu problema na minha Intel Core 2 Duo, e eu olhei para o código de montagem. Aqui está o que está acontecendo: quando o compilador avalia t1 + t2, ele faz

load t1 into an 80-bit register
load t2 into an 80-bit register
compute the 80-bit sum

Quando se armazena em sum_2que ele faz

round the 80-bit sum to a 64-bit number and store it

Em seguida, a ==comparação compara a soma de 80 bits a uma soma de 64 bits, e eles são diferentes, principalmente porque a parte fracionária 0,3 não pode ser representado exatamente usando um número de ponto flutuante binário, então você está comparando a 'decimal repetindo' ( na verdade repetindo binário) que foi truncada para dois comprimentos diferentes.

O que é realmente irritante é que se você compilador com gcc -O1ou gcc -O2, gcc faz a aritmética errado em tempo de compilação, eo problema desaparece. Talvez este é OK de acordo com o padrão, mas é apenas mais uma razão que gcc não é meu compilador favorito.


PS Quando eu digo que ==compara uma soma de 80 bits com uma soma de 64 bits, é claro que eu realmente quero dizer isso compara a versão estendida da soma de 64 bits. Você pode fazer bem para pensar

sum_2 == t1 + t2

resolve

extend(sum_2) == extend(t1) + extend(t2)

e

sum_2 = t1 + t2

resolve

sum_2 = round(extend(t1) + extend(t2))

Bem-vindo ao maravilhoso mundo do ponto flutuante!

Respondeu 27/08/2009 em 00:46
fonte usuário

votos
2

Pode ser que em um dos casos, você acaba comparando um 64-bit dupla a um registro interno de 80 bits. Pode ser esclarecedor olhar para as instruções de montagem GCC emite para os dois casos ...

Respondeu 27/08/2009 em 00:12
fonte usuário

votos
1

Comparações de números de precisão dupla são inerentemente impreciso. Por exemplo, você pode encontrar muitas vezes 0.0 == 0.0retornando falso . Isto é devido à forma como as lojas FPU e números de faixas.

Wikipedia diz :

Teste para a igualdade é problemática. Duas sequências computacionais que são matematicamente equivalente pode também produzir diferentes valores de ponto flutuante.

Você vai precisar usar um delta para dar uma tolerância para suas comparações, ao invés de um valor exato.

Respondeu 27/08/2009 em 00:08
fonte usuário

votos
0

Este "problema" pode ser "fixo" usando estas opções:

-msse2 -mfpmath = sse

como explicados nesta página:

http://www.network-theory.co.uk/docs/gccintro/gccintro_70.html

Uma vez eu usei essas opções, tanto afirma passou.

Respondeu 27/08/2009 em 22:52
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more