Pergunta

O seguinte código em C # (Net 3.5 SP1) é um ciclo infinito na minha máquina:

for (float i = 0; i < float.MaxValue; i++) ;

Ele alcançou o número 16777216,0 e 16777216.0 + 1 é avaliada como 16777216,0. No entanto, neste ponto:. I + 1 = i

Esta é uma loucura.

Eu percebo há alguma imprecisão na forma como números de ponto flutuante são armazenados. E eu li que números inteiros maiores 2 ^ 24 do que não pode ser adequadamente armazenado como um float.

Ainda assim, o código acima, deverá ser válido em C # mesmo que o número não pode ser adequadamente representada.

Por que não funciona?

Você pode obter o mesmo aconteça para o dobro, mas leva um tempo muito longo. 9007199254740992,0 é o limite para o dobro.

Foi útil?

Solução

Certo, então o problema é que, a fim de adicionar um para o flutuador, ele teria de se tornar

16777217.0

Acontece que este é em um limite para a raiz e não pode ser representado exatamente como um float. (O próximo valor mais alto disponível é 16777218.0)

Assim, arredonda para o mais próximo representável flutuador

16777216.0

Deixe-me colocar desta forma:

Uma vez que você tem um flutuante quantidade de precisão, você tem que incrementar-se por um-e superior de maior número.

EDIT:

Ok, isso é um pouco difícil de explicar, mas tente o seguinte:

float f = float.MaxValue;
f -= 1.0f;
Debug.Assert(f == float.MaxValue);

Isto irá correr bem, porque nesse valor, a fim de representar uma diferença de 1.0f, você precisaria de mais de 128 bits de precisão. Um flutuador tem apenas 32 bits.

EDIT2

Pelos meus cálculos, pelo menos 128 dígitos binários sem assinatura seria necessário.

log(3.40282347E+38) * log(10) / log(2) = 128

Como uma solução para o seu problema, você poderia loop através de dois números de 128 bits. No entanto, isso vai levar pelo menos uma década para ser concluído.

Outras dicas

Imagine por exemplo que um número de ponto flutuante é representado por até 2 dígitos decimais significativos, além de um expoente: nesse caso, você poderia contar 0-99 exatamente. O próximo seria 100, mas porque você só pode ter 2 dígitos significativos que seriam armazenados como "1,0 vezes 10 elevado à potência de 2". Somando-se ao que seria ... o quê?

Na melhor das hipóteses, seria 101 como um resultado intermediário, que seria realmente ser armazenados (via um erro de arredondamento que descarta o insignificante terceiro dígito) como "1,0 vezes 10 elevado à potência de 2" novamente.

Para entender o que está acontecendo de errado você vai ter que ler o padrão IEEE sobre ponto flutuante

Vamos examinar a estrutura do número um ponto flutuante por um segundo:

Um número de ponto flutuante é dividido em duas partes (ok 3, mas ignorar o bit de sinal para um segundo).

Você tem um expoente e mantissa. Como assim:

smmmmmmmmeeeeeee

Nota:. Que não é acurate para o número de bits, mas dá-lhe uma idéia geral do que está acontecendo

Para descobrir o número que você tem que fazer o seguinte cálculo:

mmmmmm * 2^(eeeeee) * (-1)^s

Então, o que é float.MaxValue vai ser? Bem, você vai ter o maior mantissa possível e o maior expoente possível. Vamos fingir que isso é algo como:

01111111111111111

na realidade nós definimos NAN e + -Inf e um par de outras convenções, mas ignorá-los por um segundo, porque eles não são relevantes para a sua pergunta.

Então, o que acontece quando você tem 9.9999*2^99 + 1? Bem, você não tem algarismos significativos o suficiente para acrescentar 1. Como resultado, obtém-se arredondado para o mesmo número. No caso de precisão simples de ponto flutuante o ponto em que +1 começa a descer arredondado passa a ser 16777216.0

Não tem nada a ver com o excesso, ou estar perto do valor máximo. O valor float para 16777216,0 tem uma representação binária do 16777216. Você, então incrementá-lo por 1, por isso deve ser 16777217,0, exceto que a representação binária de 16777217,0 é 16777216 !!! Então, ele realmente não se incrementado ou, pelo menos, o incremento não faz o que você espera.

Aqui está uma classe escrito por Jon Skeet que ilustra isso:

DoubleConverter.cs

Tente este código com ele:

double d1 = 16777217.0;
Console.WriteLine(DoubleConverter.ToExactString(d1));

float f1 = 16777216.0f;
Console.WriteLine(DoubleConverter.ToExactString(f1));

float f2 = 16777217.0f;
Console.WriteLine(DoubleConverter.ToExactString(f2));

Observe como a representação interna de 16777216,0 é o mesmo 16777217,0 !!

A iteração quando eu aproxima float.MaxValue tem i logo abaixo deste valor. A próxima iteração acrescenta ao i, mas não pode conter um número maior do que float.MaxValue. Assim que detém um valor muito menor, e começa o ciclo novamente.

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