Por que alguns números perdem a precisão quando armazenados como números de ponto flutuante?
Por exemplo, o número decimal 9.2
pode ser expresso exatamente como uma razão de dois números inteiros decimais ( 92/10
), os quais podem ser expressos exatamente em binário ( 0b1011100/0b1010
). No entanto, a mesma proporção armazenada como um número de ponto flutuante nunca é exatamente igual a 9.2
:
32-bit "single precision" float: 9.19999980926513671875
64-bit "double precision" float: 9.199999999999999289457264239899814128875732421875
Como um número aparentemente simples pode ser "grande demais" para expressar em 64 bits de memória?
floating-point
language-agnostic
precision
mhlester
fonte
fonte
Respostas:
Na maioria das linguagens de programação, os números de ponto flutuante são representados de maneira semelhante à notação científica : com um expoente e uma mantissa (também chamada de significando). Um número muito simples, digamos
9.2
, é realmente essa fração:Onde está o expoente
-49
e a mantissa5179139571476070
. A razão pela qual é impossível representar alguns números decimais dessa maneira é que o expoente e a mantissa devem ser inteiros. Em outras palavras, todos os carros alegóricos devem ser um número inteiro multiplicado por uma potência inteira de 2 .9.2
pode ser simples92/10
, mas 10 não pode ser expresso como 2 n se n for limitado a valores inteiros.Vendo os dados
Primeiro, algumas funções para ver os componentes que compõem um 32 e 64 bits
float
. Passe por cima deles se você se importa apenas com a saída (exemplo em Python):Há muita complexidade por trás dessa função, e seria bastante tangível de explicar, mas se você estiver interessado, o recurso importante para nossos propósitos é o módulo struct .
O Python's
float
é um número de precisão dupla de 64 bits. Em outras linguagens como C, C ++, Java e C #, a precisão dupla tem um tipo separadodouble
, que é frequentemente implementado como 64 bits.Quando chamamos essa função com o nosso exemplo
9.2
, aqui está o que obtemos:Interpretando os dados
Você verá que eu dividi o valor de retorno em três componentes. Esses componentes são:
Placa
O sinal é armazenado no primeiro componente como um único bit. É fácil de explicar:
0
significa que o flutuador é um número positivo;1
significa que é negativo. Porque9.2
é positivo, nosso valor de sinal é0
.Expoente
O expoente é armazenado no componente do meio como 11 bits. No nosso caso
0b10000000010
,. Em decimal, isso representa o valor1026
. Uma peculiaridade desse componente é que você deve subtrair um número igual a 2 (# de bits) - 1 - 1 para obter o expoente verdadeiro; no nosso caso, isso significa subtrair0b1111111111
(número decimal1023
) para obter o expoente verdadeiro0b00000000011
(número decimal 3).Mantissa
A mantissa é armazenada no terceiro componente como 52 bits. No entanto, há uma peculiaridade nesse componente também. Para entender essa peculiaridade, considere um número em notação científica, assim:
A mantissa seria a
6.0221413
. Lembre-se de que a mantissa na notação científica sempre começa com um único dígito diferente de zero. O mesmo vale para o binário, exceto que o binário possui apenas dois dígitos:0
e1
. Assim, a mantissa binária sempre começa com1
! Quando um flutuador é armazenado, a1
parte frontal da mantissa binária é omitida para economizar espaço; temos que colocá-lo de volta na frente do nosso terceiro elemento para obter a verdadeira mantissa:Isso envolve mais do que apenas uma simples adição, porque os bits armazenados em nosso terceiro componente representam, na verdade, a parte fracionária da mantissa, à direita do ponto de raiz .
Ao lidar com números decimais, "movemos o ponto decimal" multiplicando ou dividindo por potências de 10. Em binário, podemos fazer o mesmo multiplicando ou dividindo por potências de 2. Como nosso terceiro elemento possui 52 bits, dividimos por 2 52 para movê-lo 52 lugares para a direita:
Em notação decimal, é o mesmo que dividir
675539944105574
por4503599627370496
obter0.1499999999999999
. (Este é um exemplo de uma proporção que pode ser expressa exatamente em binário, mas apenas aproximadamente em decimal; para obter mais detalhes, consulte: 675539944105574/4503599627370496 .)Agora que transformamos o terceiro componente em um número fracionário, a adição
1
fornece a verdadeira mantissa.Recapitulando os componentes
0
para positivo,1
para negativo1
para obter a verdadeira mantissaCálculo do número
Juntando todas as três partes, recebemos este número binário:
Que podemos então converter de binário em decimal:
E multiplique para revelar a representação final do número com o qual começamos (
9.2
) depois de ser armazenado como um valor de ponto flutuante:Representando como uma fração
9.2
Agora que criamos o número, é possível reconstruí-lo em uma fração simples:
Mude a mantissa para um número inteiro:
Converter em decimal:
Subtraia o expoente:
Transforme expoente negativo em divisão:
Multiplicar expoente:
Qual é igual a:
9,5
Já é possível ver que a mantissa tem apenas 4 dígitos seguidos por muitos zeros. Mas vamos percorrer os passos.
Monte a notação científica binária:
Mude o ponto decimal:
Subtraia o expoente:
Binário para decimal:
Expoente negativo para divisão:
Multiplicar expoente:
É igual a:
Leitura adicional
fonte
Esta não é uma resposta completa (o mhlester já cobriu muitos bons aspectos que não duplicarei), mas gostaria de enfatizar o quanto a representação de um número depende da base em que você está trabalhando.
Considere a fração 2/3
Na boa e velha base 10, normalmente a escrevemos como algo como
Quando olhamos para essas representações, tendemos a associar cada uma delas à fração 2/3, mesmo que apenas a primeira representação seja matematicamente igual à fração. A segunda e a terceira representações / aproximações apresentam um erro da ordem de 0,001, que na verdade é muito pior que o erro entre 9.2 e 9.1999999999999993. De fato, a segunda representação nem é arredondada corretamente! No entanto, não temos um problema com 0,666 como uma aproximação do número 2/3; portanto, não devemos realmente ter um problema com a aproximação da 9.2 na maioria dos programas . (Sim, em alguns programas é importante.)
Bases numéricas
Então aqui é onde as bases numéricas são cruciais. Se estávamos tentando representar 2/3 na base 3, então
Em outras palavras, temos uma representação exata e finita para o mesmo número trocando de base! A conclusão é que, embora você possa converter qualquer número em qualquer base, todos os números racionais têm representações finitas exatas em algumas bases, mas não em outras .
Para levar esse ponto para casa, vejamos 1/2. Pode surpreendê-lo que, embora esse número perfeitamente simples tenha uma representação exata na base 10 e 2, ele exija uma representação repetida na base 3.
Por que os números de ponto flutuante são imprecisos?
Como muitas vezes, eles são racionais aproximados que não podem ser representados finitamente na base 2 (os dígitos se repetem) e, em geral, estão aproximando números reais (possivelmente irracionais) que podem não ser representáveis em muitos dígitos finitos em qualquer base.
fonte
1/3
assim como a base-10 é perfeita1/10
. Nenhuma fração funciona em base-2N
ou é um múltiplo dele.π
etc serem cancelados.Embora todas as outras respostas sejam boas, ainda falta uma coisa:
É impossível para representar números irracionais (por exemplo π,
sqrt(2)
,log(3)
, etc.) precisamente!E é por isso que eles são chamados irracionais. Nenhuma quantidade de armazenamento de bits no mundo seria suficiente para armazenar um deles. Somente a aritmética simbólica é capaz de preservar sua precisão.
Embora se você limitar suas necessidades matemáticas a números racionais, apenas o problema da precisão se tornará gerenciável. Você precisaria armazenar um par de números inteiros (possivelmente muito grandes)
a
eb
manter o número representado pela fraçãoa/b
. Toda a sua aritmética teria que ser feita em frações, como na matemática do ensino médio (por exemploa/b * c/d = ac/bd
).Mas é claro que você ainda iria correr para o mesmo tipo de problemas quando
pi
,sqrt
,log
,sin
, etc. estão envolvidos.TL; DR
Para aritmética acelerada por hardware, apenas uma quantidade limitada de números racionais pode ser representada. Todo número não representável é aproximado. Alguns números (isto é, irracionais) nunca podem ser representados, não importa o sistema.
fonte
Existem infinitos números reais (tantos que você não pode enumerá-los) e existem infinitamente muitos números racionais (é possível enumerá-los).
A representação de ponto flutuante é finita (como qualquer coisa em um computador); assim, inevitavelmente, muitos números são impossíveis de representar. Em particular, 64 bits apenas permitem distinguir entre apenas 18.446.744.073.709.551.616 valores diferentes (o que não é nada comparado ao infinito). Com a convenção padrão, 9.2 não é um deles. Os que podem têm a forma m.2 ^ e para alguns números inteiros me.
Você pode criar um sistema de numeração diferente, 10 baseado, por exemplo, em que o 9.2 teria uma representação exata. Mas outros números, digamos 1/3, ainda seriam impossíveis de representar.
Observe também que os números de ponto flutuante de precisão dupla são extremamente precisos. Eles podem representar qualquer número em uma faixa muito ampla, com até 15 dígitos exatos. Para cálculos da vida diária, 4 ou 5 dígitos são mais que suficientes. Você realmente nunca precisará desses 15, a menos que queira contar cada milissegundo de sua vida.
fonte
Os números de ponto flutuante são (simplificando levemente) um sistema de numeração posicional com um número restrito de dígitos e um ponto de raiz móvel.
Uma fração só pode ser expressa exatamente usando um número finito de dígitos em um sistema de numeração posicional se os fatores primos do denominador (quando a fração é expressa em termos mais baixos) são fatores da base.
Os fatores primos de 10 são 5 e 2; portanto, na base 10, podemos representar qualquer fração da forma a / (2 b 5 c ).
Por outro lado, o único fator primo de 2 é 2, portanto, na base 2, podemos representar apenas frações da forma a / (2 b )
Porque é um formato simples de trabalhar e é suficientemente preciso para a maioria dos propósitos. Basicamente, o mesmo motivo pelo qual os cientistas usam a "notação científica" e arredondam seus resultados para um número razoável de dígitos em cada etapa.
Certamente seria possível definir um formato de fração, com (por exemplo) um numerador de 32 bits e um denominador de 32 bits. Seria capaz de representar números que o ponto flutuante de precisão dupla IEEE não poderia, mas igualmente haveria muitos números que podem ser representados no ponto flutuante de precisão dupla que não poderiam ser representados em um formato de fração de tamanho fixo.
No entanto, o grande problema é que esse formato é uma tarefa difícil de fazer cálculos. Por duas razões.
Alguns idiomas oferecem tipos de fração, mas geralmente eles fazem isso em combinação com precisão arbitrária, isso evita a necessidade de se preocupar com a aproximação de frações, mas cria seu próprio problema, quando um número passa por um grande número de etapas de cálculo do tamanho do denominador e portanto, o armazenamento necessário para a fração pode explodir.
Alguns idiomas também oferecem tipos de ponto flutuante decimal, sendo usados principalmente em cenários em que é importante que os resultados obtidos pelo computador correspondam às regras de arredondamento pré-existentes que foram escritas com os seres humanos em mente (principalmente cálculos financeiros). É um pouco mais difícil trabalhar com isso do que o ponto flutuante binário, mas o maior problema é que a maioria dos computadores não oferece suporte a hardware.
fonte
Tente isto
'
decimalValue
' é o seu valor para converter.fonte