Pergunta

Vamos,

float dt;

Eu leio dt de um arquivo de texto como

inputFile >> dt;

Então eu tenho um for loop como,

for (float time=dt; time<=maxTime; time+=dt)
{
    // some stuff
}

Quando dt=0.05 e eu produzo std::cout << time << std::endl; Eu obtive,

0.05
0.10
...
7.00001
7.05001
...

Então, por que o número de dígitos aumenta depois de um tempo?

Foi útil?

Solução

Porque nem todo número pode ser representado por valores de ponto flutuante IEEE754.Em algum momento, você receberá um número que não é totalmente representável e o computador terá que escolher o mais próximo.

Se você inserir 0,05 em Harald Schmidt's excellent online converter e faça referência ao Entrada da Wikipedia em IEEE754-1985, você terminará com os seguintes bits (minha explicação a seguir):

   s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm
   0 01111010 10011001100110011001101
     |||||||| |||||||||||||||||||||||
128 -+||||||| ||||||||||||||||||||||+- 1 / 8388608
 64 --+|||||| |||||||||||||||||||||+-- 1 / 4194304
 32 ---+||||| ||||||||||||||||||||+--- 1 / 2097152
 16 ----+|||| |||||||||||||||||||+---- 1 / 1048576
  8 -----+||| ||||||||||||||||||+----- 1 /  524288
  4 ------+|| |||||||||||||||||+------ 1 /  262144
  2 -------+| ||||||||||||||||+------- 1 /  131072
  1 --------+ |||||||||||||||+-------- 1 /   65536
              ||||||||||||||+--------- 1 /   32768
              |||||||||||||+---------- 1 /   16384
              ||||||||||||+----------- 1 /    8192
              |||||||||||+------------ 1 /    4096
              ||||||||||+------------- 1 /    2048
              |||||||||+-------------- 1 /    1024
              ||||||||+--------------- 1 /     512
              |||||||+---------------- 1 /     256
              ||||||+----------------- 1 /     128
              |||||+------------------ 1 /      64
              ||||+------------------- 1 /      32
              |||+-------------------- 1 /      16
              ||+--------------------- 1 /       8
              |+---------------------- 1 /       4
              +----------------------- 1 /       2

O sinal, sendo 0, é positivo.O expoente é indicado pelo mapeamento de um bit para os números à esquerda: 64+32+16+8+2 = 122 - 127 bias = -5, então o multiplicador é 2-5 ou 1/32.O 127 o viés é permitir a representação de números muito pequenos (como próximos de zero, em vez de números negativos com grande magnitude).

A mantissa é um pouco mais complicada.Para cada bit, você acumula os números no lado direito (depois de adicionar um implícito 1).Portanto, você pode calcular o número como a soma de {1, 1/2, 1/16, 1/32, 1/256, 1/512, 1/4096, 1/8192, 1/65536, 1/131072, 1/1048576, 1/2097152, 1/8388608}.

Quando você soma tudo isso, você obtém 1.60000002384185791015625.

Quando você multiplica que pelo multiplicador 1/32 (calculado anteriormente a partir dos bits do expoente), você obtém 0.0500000001, então você pode ver isso 0.05 é não representado exatamente.Este padrão de bits para a mantissa é na verdade o mesmo que 0.1 mas, com isso, o expoente é -4 em vez de -5, e é por isso 0.1 + 0.1 + 0.1 raramente é igual a 0.3 (esta parece ser uma pergunta favorita da entrevista).

Quando você começar a somar, esse pequeno erro se acumulará, pois você não apenas verá um erro no 0.05 em si, erros também podem ser introduzidos em cada estágio da acumulação - nem todos os números 0.1, 0.15, 0.2 e assim por diante também podem ser representados exatamente.

Eventualmente, os erros ficarão grandes o suficiente para começarem a aparecer no número se você usar a precisão padrão.Você pode adiar um pouco escolhendo sua própria precisão com algo como:

#include <iostream>
#include <iomanip>
:
std::cout << std::setprecison (2) << time << '\n';

Isso não corrigirá a variável valor, mas isso lhe dará mais espaço para respirar antes que os erros se tornem visíveis.

Além disso, algumas pessoas recomendam evitar std::endl uma vez que força uma descarga dos buffers.Se sua implementação estiver se comportando bem, isso acontecerá com os dispositivos terminais quando você enviar uma nova linha de qualquer maneira.E se você redirecionou a saída padrão para um não terminal, provavelmente não quero liberar em todas as linhas.Não é realmente relevante para a sua pergunta e provavelmente não fará muita diferença na grande maioria dos casos, apenas um ponto que pensei em abordar.

Outras dicas

Os carros flutuantes IEEE usam o sistema numérico binário e, portanto, não podem armazenar números decimais com exatidão.Quando você soma vários deles (às vezes apenas dois são suficientes), os erros de representação podem se acumular e se tornar visíveis.

Alguns números não podem ser representados com precisão usando pontos flutuantes OU números de base 2.Se bem me lembro, um desses números é decimal 0,05 (na base 2 resulta em infinitamente repetição de número fracionário).Outro problema é que se você imprimir o ponto flutuante no arquivo (como número de base 10) e depois lê-lo novamente, poderá obter um número diferente - porque a base é diferente e isso pode causar problemas ao converter o número base2 fracionário em número base10 fracionário.

Se você quiser melhor precisão, tente procurar uma biblioteca bignum.No entanto, isso funcionará muito mais lentamente que os pontos flutuantes.Outra maneira de lidar com problemas de precisão seria tentar armazenar números como "fração comum" com numerador/denominador (ou seja,1/10 em vez de 0,1, 1/3 em vez de 0,333 .., etc - provavelmente existe uma biblioteca até para isso, mas nunca ouvi falar), mas isso não funcionará com números irracionais como pi ou e.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top