Precisão e precisão nos cálculos de vírgula flutuante

Número original da BDC: 125056

Resumo

Existem muitas situações em que a precisão, o arredondamento e a precisão nos cálculos de vírgula flutuante podem funcionar para gerar resultados surpreendentes para o programador. Devem seguir as quatro regras gerais:

  1. Num cálculo que envolve precisão única e dupla, o resultado não será normalmente mais preciso do que a precisão única. Se for necessária uma precisão dupla, certifique-se de que todos os termos no cálculo, incluindo constantes, são especificados em precisão dupla.

  2. Nunca suponha que um valor numérico simples é representado com precisão no computador. A maioria dos valores de vírgula flutuante não pode ser representada com precisão como um valor binário finito. Por exemplo, .1 está .0001100110011... no binário (repete-se para sempre), pelo que não pode ser representado com total precisão num computador com aritmética binária, que inclui todos os PCs.

  3. Nunca suponha que o resultado é preciso para a última casa decimal. Existem sempre pequenas diferenças entre a resposta "verdadeira" e o que pode ser calculado com a precisão finita de qualquer unidade de processamento de vírgula flutuante.

  4. Nunca compare dois valores de vírgula flutuante para ver se são iguais ou não iguais. Este é um corolário para a regra 3. Quase sempre haverá pequenas diferenças entre números que "devem" ser iguais. Em vez disso, verifique sempre se os números são quase iguais. Por outras palavras, verifique se a diferença entre as mesmas é pequena ou insignificante.

Mais Informações

Em geral, as regras descritas acima aplicam-se a todos os idiomas, incluindo C, C++, e assembler. Os exemplos abaixo demonstram algumas das regras que utilizam o PowerStation FORTRAN. Todos os exemplos foram compilados com o FortRAN PowerStation 32 sem opções, exceto a última, que está escrita em C.

Exemplo 1

O primeiro exemplo demonstra duas coisas:

  • As constantes FORTRAN são de precisão única por predefinição (por predefinição, as constantes C são de precisão dupla).
  • Os cálculos que contêm termos de precisão únicos não são muito mais precisos do que os cálculos em que todos os termos são de precisão única.

Depois de ser inicializado com 1,1 (uma única constante de precisão), y é tão impreciso como uma única variável de precisão.

x = 1.100000000000000  y = 1.100000023841858

O resultado da multiplicação de um único valor de precisão por um valor de precisão dupla preciso é quase tão mau como multiplicar dois valores de precisão única. Ambos os cálculos têm milhares de vezes mais erros do que multiplicar dois valores de precisão dupla.

true = 1.320000000000000 (multiplying 2 double precision values)
y    = 1.320000052452087 (multiplying a double and a single)
z    = 1.320000081062318 (multiplying 2 single precision values)

Código de exemplo

C Compile options: none

       real*8 x,y,z
       x = 1.1D0
       y = 1.1
       print *, 'x =',x, 'y =', y
       y = 1.2 * x
       z = 1.2 * 1.1
       print *, x, y, z
       end

Exemplo 2

A amostra 2 utiliza a equação quadrática. Demonstra que mesmo cálculos de precisão dupla não são perfeitos e que o resultado de um cálculo deve ser testado antes de depender se pequenos erros podem ter resultados drásticos. A entrada para a função raiz quadrada na amostra 2 é apenas ligeiramente negativa, mas ainda é inválida. Se os cálculos de precisão dupla não tiverem erros ligeiros, o resultado seria:

Root =   -1.1500000000

Em vez disso, gera o seguinte erro:

erro de tempo de execução M6201: MATEMÁTICA

  • sqrt: Erro de DOMÍNIO

Código de exemplo

C Compile options: none

       real*8 a,b,c,x,y
       a=1.0D0
       b=2.3D0
       c=1.322D0
       x = b**2
       y = 4*a*c
       print *,x,y,x-y
       print "(' Root =',F16.10)",(-b+dsqrt(x-y))/(2*a)
       end

Exemplo 3

A amostra 3 demonstra que, devido a otimizações que ocorrem mesmo que a otimização não esteja ativada, os valores podem manter temporariamente uma precisão superior ao esperado e que é imprudente testar dois valores de vírgula flutuante para igualdade.

Neste exemplo, dois valores são iguais e não iguais. No primeiro SE, o valor de Z ainda está na pilha do coprocessador e tem a mesma precisão que Y. Portanto, X não é igual a Y e a primeira mensagem é impressa. No momento do segundo SE, Z teve de ser carregado a partir da memória e, portanto, tinha a mesma precisão e valor que X e a segunda mensagem também é impressa.

Código de exemplo

C Compile options: none

       real*8 y
       y=27.1024D0
       x=27.1024
       z=y
       if (x.ne.z) then
         print *,'X does not equal Z'
       end if
       if (x.eq.z) then
         print *,'X equals Z'
       end if
       end

Exemplo 4

A primeira parte do código de exemplo 4 calcula a menor diferença possível entre dois números próximos de 1,0. Fá-lo ao adicionar um único bit à representação binária de 1.0.

x   = 1.00000000000000000  (one bit more than 1.0)
y   = 1.00000000000000000  (exactly 1.0)
x-y =  .00000000000000022  (smallest possible difference)

Algumas versões do FORTRAN arredondam os números ao apresentá-los para que a imprecisão numérica inerente não seja tão óbvia. É por isso que x e y têm o mesmo aspeto quando apresentados.

A segunda parte do código de exemplo 4 calcula a menor diferença possível entre dois números próximos de 10,0. Mais uma vez, faz isto ao adicionar um único bit à representação binária de 10,0. Repare que a diferença entre números próximos de 10 é maior do que a diferença perto de 1. Isto demonstra o princípio geral de que quanto maior for o valor absoluto de um número, menos precisamente pode ser armazenado num determinado número de bits.

x   = 10.00000000000000000  (one bit more than 10.0)
y   = 10.00000000000000000  (exactly 10.0)
x-y =   .00000000000000178

A representação binária destes números também é apresentada para mostrar que diferem apenas 1 bit.

x = 4024000000000001 Hex
y = 4024000000000000 Hex

A última parte do código de exemplo 4 mostra que os valores decimais simples que não se repetem podem, muitas vezes, ser representados em binário apenas por uma fração de repetição. Neste caso, x=1,05, que requer um fator de repetição CCCCCCCC.... (Hex) na mantissa. Em FORTRAN, o último dígito "C" é arredondado para "D" para manter a precisão mais elevada possível:

x = 3FF0CCCCCCCCCCCD (Hex representation of 1.05D0)

Mesmo após o arredondamento, o resultado não é perfeitamente preciso. Ocorre um erro após o dígito menos significativo, que podemos ver ao remover o primeiro dígito.

x-1 = .05000000000000004

Código de exemplo

C Compile options: none

       IMPLICIT real*8 (A-Z)
       integer*4 i(2)
       real*8 x,y
       equivalence (i(1),x)

       x=1.
       y=x
       i(1)=i(1)+1
       print "(1x,'x  =',F20.17,'  y=',f20.17)", x,y
       print "(1x,'x-y=',F20.17)", x-y
       print *

       x=10.
       y=x
       i(1)=i(1)+1
       print "(1x,'x  =',F20.17,'  y=',f20.17)", x,y
       print "(1x,'x-y=',F20.17)", x-y
       print *
       print "(1x,'x  =',Z16,' Hex  y=',Z16,' Hex')", x,y
       print *

       x=1.05D0
       print "(1x,'x  =',F20.17)", x
       print "(1x,'x  =',Z16,' Hex')", x
       x=x-1
       print "(1x,'x-1=',F20.17)", x
       print *
       end

Exemplo 5

Em C, as constantes flutuantes são duplicadas por predefinição. Utilize um "f" para indicar um valor flutuante, como em "89,95f".

/* Compile options needed: none
*/ 

#include <stdio.h>

void main()
   {
      float floatvar;
      double doublevar;

/* Print double constant. */ 
      printf("89.95 = %f\n", 89.95);      // 89.95 = 89.950000

/* Printf float constant */ 
      printf("89.95 = %f\n", 89.95F);     // 89.95 = 89.949997

/*** Use double constant. ***/ 
      floatvar = 89.95;
      doublevar = 89.95;

printf("89.95 = %f\n", floatvar);   // 89.95 = 89.949997
      printf("89.95 = %lf\n", doublevar); // 89.95 = 89.950000

/*** Use float constant. ***/ 
      floatvar = 89.95f;
      doublevar = 89.95f;

printf("89.95 = %f\n", floatvar);   // 89.95 = 89.949997
      printf("89.95 = %lf\n", doublevar); // 89.95 = 89.949997
   }