Considere o seguinte código:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
Por que essas imprecisões acontecem?
math
language-agnostic
floating-point
floating-accuracy
Cato Johnston
fonte
fonte
Respostas:
A matemática binária de ponto flutuante é assim. Na maioria das linguagens de programação, ela é baseada no padrão IEEE 754 . O ponto crucial do problema é que os números são representados nesse formato como um número inteiro vezes a potência de dois; números racionais (como
0.1
, o que é1/10
) cujo denominador não é um poder de dois não podem ser exatamente representados.No formato
0.1
padrãobinary64
, a representação pode ser escrita exatamente como0.1000000000000000055511151231257827021181583404541015625
em decimal ou0x1.999999999999ap-4
na notação hexadecimal C99 .Por outro lado, o número racional
0.1
, ou seja1/10
, pode ser escrito exatamente como0.1
em decimal ou0x1.99999999999999...p-4
em um análogo da notação hexadecimal C99, em que o...
representa uma sequência interminável de 9's.As constantes
0.2
e0.3
no seu programa também serão aproximações aos seus valores verdadeiros. Acontece que o mais próximodouble
que0.2
é maior do que o número racional0.2
, mas que o mais próximodouble
que0.3
é menor do que o número racional0.3
. A soma de0.1
e0.2
acaba sendo maior que o número racional0.3
e, portanto, discordando da constante em seu código.Um tratamento bastante abrangente das questões aritméticas de ponto flutuante é o que todo cientista da computação deve saber sobre aritmética de ponto flutuante . Para uma explicação mais fácil de digerir, consulte floating-point-gui.de .
Nota lateral: Todos os sistemas de números posicionais (base-N) compartilham esse problema com precisão
Números decimais antigos simples (base 10) têm os mesmos problemas, e é por isso que números como 1/3 acabam como 0,333333333 ...
Você acabou de encontrar um número (3/10) que é fácil de representar com o sistema decimal, mas não se encaixa no sistema binário. Também funciona nos dois sentidos (em algum grau pequeno): 1/16 é um número feio em decimal (0,0625), mas em binário parece tão puro quanto um 10.000º em decimal (0,0001) ** - se estivéssemos em o hábito de usar um sistema numérico de base 2 em nossas vidas diárias, você até olha para esse número e instintivamente entende que pode chegar lá pela metade de algo, pela metade de novo e de novo e de novo.
** É claro que não é exatamente assim que os números de ponto flutuante são armazenados na memória (eles usam uma forma de notação científica). No entanto, ilustra o ponto em que erros binários de precisão de ponto flutuante tendem a surgir porque os números do "mundo real" com os quais geralmente estamos interessados em trabalhar são frequentemente potências de dez - mas apenas porque usamos um sistema de números decimais dia- hoje. É também por isso que diremos coisas como 71% em vez de "5 em cada 7" (71% é uma aproximação, pois 5/7 não pode ser representado exatamente com qualquer número decimal).
Portanto, não: números binários de ponto flutuante não são quebrados, eles são tão imperfeitos quanto qualquer outro sistema de números de base N :)
Side Side Note: Trabalhando com flutuadores na programação
Na prática, esse problema de precisão significa que você precisa usar funções de arredondamento para arredondar seus números de ponto flutuante para o número de casas decimais em que estiver interessado antes de exibi-los.
Você também precisa substituir os testes de igualdade por comparações que permitam certa tolerância, o que significa:
Você não fazer
if (x == y) { ... }
Em vez disso, faça
if (abs(x - y) < myToleranceValue) { ... }
.onde
abs
é o valor absoluto.myToleranceValue
precisa ser escolhido para sua aplicação específica - e terá muito a ver com a quantidade de "espaço de manobra" que você está preparado para permitir e qual será o maior número que você comparará (devido a problemas de perda de precisão ) Cuidado com as constantes de estilo "epsilon" no seu idioma preferido. Estes não devem ser usados como valores de tolerância.fonte
Perspectiva de um designer de hardware
Acredito que devo acrescentar a perspectiva de um designer de hardware a isso, pois projeto e construo hardware de ponto flutuante. Conhecer a origem do erro pode ajudar a entender o que está acontecendo no software e, finalmente, espero que isso ajude a explicar as razões pelas quais erros de ponto flutuante acontecem e parecem se acumular ao longo do tempo.
1. Visão Geral
Do ponto de vista da engenharia, a maioria das operações de ponto flutuante terá algum elemento de erro, já que o hardware que faz os cálculos de ponto flutuante só precisa ter um erro inferior a metade de uma unidade em último lugar. Portanto, muito hardware será interrompido com uma precisão necessária apenas para gerar um erro inferior a metade de uma unidade em último lugar para uma única operação, o que é especialmente problemático na divisão de ponto flutuante. O que constitui uma única operação depende de quantos operandos a unidade leva. Para a maioria, são dois, mas algumas unidades aceitam 3 ou mais operandos. Por esse motivo, não há garantia de que operações repetidas resultem em um erro desejável, pois os erros aumentam com o tempo.
2. Padrões
A maioria dos processadores segue o padrão IEEE-754 , mas alguns usam padrões não normalizados ou diferentes. Por exemplo, há um modo desnormalizado no IEEE-754 que permite a representação de números de ponto flutuante muito pequenos à custa da precisão. A seguir, no entanto, abordaremos o modo normalizado do IEEE-754, que é o modo típico de operação.
No padrão IEEE-754, os projetistas de hardware têm qualquer valor de erro / epsilon contanto que seja menos da metade de uma unidade em último lugar, e o resultado deve ser apenas menos da metade de uma unidade no último local para uma operação. Isso explica por que, quando há operações repetidas, os erros são somados. Para a precisão dupla IEEE-754, esse é o 54º bit, já que 53 bits são usados para representar a parte numérica (normalizada), também chamada de mantissa, do número de ponto flutuante (por exemplo, o 5.3 em 5.3e5). As próximas seções entram em mais detalhes sobre as causas do erro de hardware em várias operações de ponto flutuante.
3. Causa do erro de arredondamento na divisão
A principal causa do erro na divisão de ponto flutuante são os algoritmos de divisão usados para calcular o quociente. A maioria dos sistemas computacionais calcula a divisão usando multiplicação por uma inversa, principalmente em
Z=X/Y
,Z = X * (1/Y)
. Uma divisão é computada iterativamente, ou seja, cada ciclo calcula alguns bits do quociente até que a precisão desejada seja alcançada, o que para IEEE-754 é qualquer coisa com um erro menor que uma unidade em último lugar. A tabela de recíprocos de Y (1 / Y) é conhecida como tabela de seleção de quociente (QST) na divisão lenta, e o tamanho em bits da tabela de seleção de quociente geralmente é a largura do radical ou um número de bits de o quociente calculado em cada iteração, mais alguns bits de guarda. Para o padrão IEEE-754, precisão dupla (64 bits), seria o tamanho da raiz do divisor, além de alguns bits de proteção k, ondek>=2
. Assim, por exemplo, uma Tabela de Seleção de Quociente típica para um divisor que calcula 2 bits do quociente por vez (raiz 4) seria2+2= 4
bits (mais alguns bits opcionais).3.1 Erro de arredondamento de divisão: aproximação de recíproca
O que os recíprocos existem na tabela de seleção de quociente dependem do método de divisão : divisão lenta, como a divisão SRT, ou divisão rápida, como a divisão Goldschmidt; cada entrada é modificada de acordo com o algoritmo de divisão na tentativa de gerar o menor erro possível. Em qualquer caso, porém, todos os recíprocos são aproximaçõesrecíproco real e introduzir algum elemento de erro. Os métodos de divisão lenta e de divisão rápida calculam o quociente de forma iterativa, ou seja, um número de bits do quociente é calculado a cada etapa; o resultado é subtraído do dividendo e o divisor repete as etapas até que o erro seja menor que a metade de um. unidade em último lugar. Os métodos de divisão lenta calculam um número fixo de dígitos do quociente em cada etapa e geralmente são mais baratos de construir, e os métodos de divisão rápida calculam um número variável de dígitos por etapa e geralmente são mais caros de construir. A parte mais importante dos métodos de divisão é que a maioria deles se baseia na multiplicação repetida por uma aproximação de um recíproco, de modo que eles são propensos a erros.
4. Erros de arredondamento em outras operações: truncamento
Outra causa dos erros de arredondamento em todas as operações são os diferentes modos de truncamento da resposta final que o IEEE-754 permite. Há truncado, arredondado para zero, arredondado para o mais próximo (padrão), arredondado para baixo e arredondado. Todos os métodos introduzem um elemento de erro inferior a uma unidade em último lugar para uma única operação. Com o tempo e operações repetidas, o truncamento também adiciona cumulativamente o erro resultante. Esse erro de truncamento é especialmente problemático na exponenciação, que envolve alguma forma de multiplicação repetida.
5. Operações Repetidas
Como o hardware que faz os cálculos de ponto flutuante precisa apenas produzir um resultado com um erro inferior a metade de uma unidade em último lugar para uma única operação, o erro aumentará ao longo de operações repetidas se não for observado. Essa é a razão pela qual, em cálculos que exigem um erro limitado, os matemáticos usam métodos como o dígito par arredondar para o mais próximo no último local da IEEE-754, porque, com o tempo, os erros têm mais probabilidade de se cancelar. para fora, e Intervalo Aritmética combinado com as variações de IEEE 754 arredondamento modosprever erros de arredondamento e corrigi-los. Devido ao seu erro relativo baixo em comparação com outros modos de arredondamento, o arredondamento para o dígito par mais próximo (em último lugar) é o modo de arredondamento padrão do IEEE-754.
Observe que o modo de arredondamento padrão, dígito par arredondar para o mais próximo , garante um erro menor que a metade de uma unidade no último local para uma operação. O uso de truncamento, arredondamento e arredondamento sozinho pode resultar em um erro maior que a metade de uma unidade em último lugar, mas menor que uma unidade em último lugar, portanto, esses modos não são recomendados, a menos que sejam usado na aritmética de intervalos.
6. Resumo
Em suma, a razão fundamental dos erros nas operações de ponto flutuante é uma combinação do truncamento no hardware e o truncamento de um recíproco no caso de divisão. Como o padrão IEEE-754 requer apenas um erro inferior a metade de uma unidade em último lugar para uma única operação, os erros de ponto flutuante em operações repetidas serão adicionados, a menos que sejam corrigidos.
fonte
Ao converter .1 ou 1/10 para a base 2 (binária), você obtém um padrão de repetição após o ponto decimal, assim como tenta representar 1/3 na base 10. O valor não é exato e, portanto, você não pode fazer matemática exata usando métodos normais de ponto flutuante.
fonte
A maioria das respostas aqui aborda essa questão em termos técnicos muito secos. Eu gostaria de abordar isso em termos que os seres humanos normais possam entender.
Imagine que você está tentando fatiar pizzas. Você tem um cortador de pizza robótico que pode cortar fatias de pizza exatamente ao meio. Pode cortar pela metade uma pizza inteira ou pela metade uma fatia existente, mas, em qualquer caso, a metade é sempre exata.
Esse cortador de pizza tem movimentos muito finos, e se você começar com uma pizza inteira, reduza pela metade e continue cortando pela metade a menor fatia de cada vez, você pode fazer a metade pela metade 53 vezes antes que a fatia seja muito pequena para suas habilidades de alta precisão . Nesse ponto, você não pode mais dividir pela metade essa fatia muito fina, mas deve incluí-la ou excluí-la como está.
Agora, como você colocaria todas as fatias de maneira a adicionar um décimo (0,1) ou um quinto (0,2) de uma pizza? Realmente pense sobre isso e tente resolvê-lo. Você pode até tentar usar uma pizza de verdade, se tiver um cortador de pizza de precisão mítico à mão. :-)
Os programadores mais experientes, é claro, sabem a resposta real, que é que não há maneira de juntar um décimo ou quinto exato da pizza usando essas fatias, por mais finas que sejam. Você pode fazer uma aproximação muito boa e, se somar a aproximação de 0,1 com a aproximação de 0,2, obterá uma boa aproximação de 0,3, mas ainda assim é apenas uma aproximação.
Para números de precisão dupla (que é a precisão que permite reduzir pela metade a sua pizza 53 vezes), os números imediatamente menores e maiores que 0,1 são 0,09999999999999999167332731531132594682276248931884765625 e 0.1000000000000000055511151231257827021181583404541015625. O último está um pouco mais próximo de 0,1 do que o primeiro, portanto, um analisador numérico, com uma entrada de 0,1, favorece o último.
(A diferença entre esses dois números é a "menor fatia" que devemos decidir incluir, que introduz um viés para cima ou exclui, que introduz um viés para baixo. O termo técnico para essa menor fatia é uma ulp .)
No caso de 0,2, os números são todos iguais, apenas aumentados por um fator de 2. Novamente, favorecemos o valor ligeiramente superior a 0,2.
Observe que em ambos os casos, as aproximações para 0.1 e 0.2 têm um leve viés ascendente. Se adicionarmos um número suficiente desses vieses, eles empurrarão o número cada vez mais longe do que queremos e, de fato, no caso de 0,1 + 0,2, o viés é alto o suficiente para que o número resultante não seja mais o número mais próximo para 0,3.
Em particular, 0.1 + 0.2 é realmente 0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000000444089209850062616169452699759999999999999999999999999999999999999999999999999999999999999999999999999999999999999999099099
PS Algumas linguagens de programação também fornecem cortadores de pizza que podem dividir fatias em décimos exatos . Embora esses cortadores de pizza sejam incomuns, se você tiver acesso a um, use-o quando for importante obter exatamente um décimo ou um quinto de uma fatia.
(Originalmente publicado no Quora.)
fonte
Erros de arredondamento de ponto flutuante. 0.1 não pode ser representado com precisão na base-2 como na base-10 devido ao fator primo ausente de 5. Assim como 1/3 leva um número infinito de dígitos para representar em decimal, mas é "0,1" na base-3, 0.1 pega um número infinito de dígitos na base 2, onde não na base 10. E os computadores não têm uma quantidade infinita de memória.
fonte
Além das outras respostas corretas, convém dimensionar seus valores para evitar problemas com a aritmética de ponto flutuante.
Por exemplo:
... ao invés de:
A expressão
0.1 + 0.2 === 0.3
retornafalse
em JavaScript, mas felizmente a aritmética inteira em ponto flutuante é exata, portanto, erros de representação decimal podem ser evitados pelo dimensionamento.Como exemplo prático, para evitar problemas de ponto flutuante onde a precisão é primordial, é recomendável 1 manipular o dinheiro como um número inteiro representando o número de centavos:
2550
centavos em vez de25.50
dólares.1 Douglas Crockford: JavaScript: As peças boas : Apêndice A - Peças horríveis (página 105) .
fonte
Minha resposta é bastante longa, então eu a dividi em três seções. Como a questão é sobre matemática de ponto flutuante, enfatizei o que a máquina realmente faz. Também especifiquei a precisão dupla (64 bits), mas o argumento se aplica igualmente a qualquer aritmética de ponto flutuante.
Preâmbulo
Um número de formato de ponto flutuante binário de precisão dupla IEEE 754 (binary64) representa um número no formato
em 64 bits:
1
se o número for negativo,0
caso contrário 1 .1.
é sempre 2 omitido, já que o bit mais significativo de qualquer valor binário é1
.1 - O IEEE 754 permite o conceito de um zero assinado -
+0
e-0
são tratados de maneira diferente:1 / (+0)
é infinito positivo;1 / (-0)
é infinito negativo. Para valores zero, os bits mantissa e expoente são zero. Nota: os valores zero (+0 e -0) não são explicitamente classificados como desnormais 2 .2 - Este não é o caso de números desnormais , que possuem um expoente de deslocamento igual a zero (e um implícito
0.
). O intervalo de números de precisão dupla denormal é d min ≤ | x | ≤ d max , onde d min (o menor número diferente de zero representável) é 2 -1.023-51 (≈ 4,94 * 10 -324 ) e d max (o maior número denormal, para o qual a mantissa consiste inteiramente em1
s) é 2 -1023 + 1 - 2 - 1023 - 51 (~ 2,225 * 10 - 308 ).Transformando um número de precisão dupla em binário
Existem muitos conversores online para converter um número de ponto flutuante de precisão dupla em binário (por exemplo, em binaryconvert.com ), mas aqui está um código C # de amostra para obter a representação IEEE 754 para um número de precisão dupla (eu separo as três partes com dois pontos (
:
) :Chegando ao ponto: a pergunta original
(Pule para o final da versão TL; DR)
Cato Johnston (o autor da pergunta) perguntou por que 0,1 + 0,2! = 0,3.
Escritas em binário (com dois pontos separando as três partes), as representações IEEE 754 dos valores são:
Observe que a mantissa é composta por dígitos recorrentes de
0011
. Esta é a chave para porque é que há qualquer erro para os cálculos - 0,1, 0,2 e 0,3 não pode ser representada em binário precisamente em um finito número de bits binários não mais do que 1/9, 1/3 ou 1/7 pode ser representado com precisão em dígitos decimais .Observe também que podemos diminuir a potência no expoente em 52 e deslocar o ponto na representação binária para a direita em 52 lugares (semelhante a 10 -3 * 1,23 == 10 -5 * 123). Isso então nos permite representar a representação binária como o valor exato que ela representa na forma a * 2 p . onde 'a' é um número inteiro.
Converter os expoentes em decimal, remover o deslocamento e adicionar novamente o implícito
1
(entre colchetes) 0,1 e 0,2 são:Para adicionar dois números, o expoente precisa ser o mesmo, ou seja:
Como a soma não tem a forma 2 n * 1. {bbb}, aumentamos o expoente em um e mudamos o ponto decimal ( binário ) para obter:
Agora existem 53 bits na mantissa (o 53º está entre colchetes na linha acima). O padrão modo de arredondamento para IEEE 754 é ' Round to mais próxima ' - ou seja, se um número x cai entre dois valores de um e b , o valor onde o bit menos significativo é zero é escolhido.
Note-se que um e b diferem apenas no último bit;
...0011
+1
=...0100
. Nesse caso, o valor com o bit menos significativo de zero é b , portanto, a soma é:considerando que a representação binária de 0,3 é:
que difere apenas da representação binária da soma de 0,1 e 0,2 por 2 -54 .
A representação binária de 0.1 e 0.2 são as representações mais precisas dos números permitidos pelo IEEE 754. A adição dessas representações, devido ao modo de arredondamento padrão, resulta em um valor que difere apenas no bit menos significativo.
TL; DR
Escrevendo
0.1 + 0.2
em uma representação binária IEEE 754 (com dois pontos separando as três partes) e comparando-a0.3
, isto é (coloquei os bits distintos entre colchetes):Convertidos de volta para decimal, esses valores são:
A diferença é exatamente 2 -54 , que é ~ 5,5511151231258 × 10 -17 - insignificante (para muitas aplicações) quando comparada aos valores originais.
Comparar os últimos bits de um número de ponto flutuante é inerentemente perigoso, como quem ler o famoso " O que todo cientista da computação deve saber sobre aritmética de ponto flutuante " (que cobre todas as principais partes desta resposta) saberá.
A maioria das calculadoras usar adicionais dígitos de guarda de contornar este problema, que é como
0.1 + 0.2
daria0.3
: os poucos bits finais são arredondados.fonte
Os números de ponto flutuante armazenados no computador consistem em duas partes, um número inteiro e um expoente para o qual a base é levada e multiplicada pela parte inteira.
Se o computador estivesse trabalhando na base 10,
0.1
seria1 x 10⁻¹
,0.2
seria2 x 10⁻¹
e0.3
seria3 x 10⁻¹
. A matemática de números inteiros é fácil e exata, portanto a adição0.1 + 0.2
obviamente resultará em0.3
.Os computadores geralmente não funcionam na base 10, eles funcionam na base 2. Você ainda pode obter resultados exatos para alguns valores, por exemplo,
0.5
é1 x 2⁻¹
e0.25
é1 x 2⁻²
, e adicioná-los em3 x 2⁻²
, ou0.75
. Exatamente.O problema vem com números que podem ser representados exatamente na base 10, mas não na base 2. Esses números precisam ser arredondados para o equivalente mais próximo. Assumindo o formato muito comum de ponto flutuante IEEE de 64 bits, o número mais próximo de
0.1
é3602879701896397 x 2⁻⁵⁵
e o número mais próximo de0.2
é7205759403792794 x 2⁻⁵⁵
; adicionando-os resulta em10808639105689191 x 2⁻⁵⁵
, ou um valor decimal exato de0.3000000000000000444089209850062616169452667236328125
. Os números de ponto flutuante geralmente são arredondados para exibição.fonte
Erro de arredondamento do ponto flutuante. Do que todo cientista da computação deve saber sobre aritmética de ponto flutuante :
fonte
Minha solução alternativa:
precisão refere-se ao número de dígitos que você deseja preservar após o ponto decimal durante a adição.
fonte
Muitas respostas boas foram postadas, mas eu gostaria de acrescentar mais uma.
Nem todos os números podem ser representados por flutuadores / duplos. Por exemplo, o número "0,2" será representado como "0,200000003" com precisão única no padrão de ponto de flutuação IEEE754.
O modelo para números reais da loja sob o capô representa números flutuantes como
Mesmo que você possa digitar
0.2
facilmente,FLT_RADIX
eDBL_RADIX
é 2; não 10 para um computador com FPU que use "Padrão IEEE para aritmética binária de ponto flutuante (ISO / IEEE Std 754-1985)".Portanto, é um pouco difícil representar exatamente esses números. Mesmo se você especificar esta variável explicitamente sem nenhum cálculo intermediário.
fonte
Algumas estatísticas relacionadas a essa famosa questão de precisão dupla.
Ao adicionar todos os valores ( a + b ) usando uma etapa de 0,1 (de 0,1 a 100), temos ~ 15% de chance de erro de precisão . Observe que o erro pode resultar em valores um pouco maiores ou menores. aqui estão alguns exemplos:
Ao subtrair todos os valores ( a - b onde a> b ) usando uma etapa de 0,1 (de 100 a 0,1), temos ~ 34% de chance de erro de precisão . aqui estão alguns exemplos:
* 15% e 34% são realmente enormes; portanto, sempre use BigDecimal quando a precisão for de grande importância. Com 2 dígitos decimais (etapa 0,01), a situação piora um pouco mais (18% e 36%).
fonte
Não, não está quebrado, mas a maioria das frações decimais deve ser aproximada
A aritmética de ponto flutuante é exata, infelizmente, não combina bem com a nossa representação habitual de números da base 10, portanto, geralmente estamos fornecendo uma entrada um pouco diferente do que escrevemos.
Mesmo números simples como 0,01, 0,02, 0,03, 0,04 ... 0,24 não são representáveis exatamente como frações binárias. Se você contar 0,01, 0,02, 0,03 ..., até chegar a 0,25, você obterá a primeira fração representável na base 2 . Se você tentasse usar o FP, seu 0,01 teria sido um pouco desligado; portanto, a única maneira de adicionar 25 deles a um bom exato 0,25 exigiria uma longa cadeia de causalidade envolvendo bits de proteção e arredondamento. É difícil prever, então levantamos as mãos e dizemos "FP é inexato", mas isso não é verdade.
Damos constantemente ao hardware FP algo que parece simples na base 10, mas que é uma fração repetida na base 2.
Quando escrevemos em decimal, toda fração (especificamente, todo decimal que termina) é um número racional da forma
a / (2 n x 5 m )
Em binário, obtemos apenas o termo 2 n , ou seja:
a / 2 n
Assim, em decimal, não podemos representar 1 / 3 . Como a base 10 inclui 2 como fator primordial, todo número que podemos escrever como uma fração binária também pode ser escrito como uma fração base 10. No entanto, quase tudo o que escrevemos como uma fração de base 10 é representável em binário. No intervalo de 0,01, 0,02, 0,03 ... 0,99, apenas três números podem ser representados em nosso formato FP: 0,25, 0,50 e 0,75, porque são 1/4, 1/2 e 3/4, todos os números com um fator primordial usando apenas o termo 2 n .
Na base de 10 nós não pode representar 1 / 3 . Mas em binário, não podemos fazer 1 / 10 ou 1 / 3 .
Portanto, embora toda fração binária possa ser escrita em decimal, o inverso não é verdadeiro. E, de fato, a maioria das frações decimais se repete em binário.
Os desenvolvedores geralmente são instruídos a fazer comparações <épsilon , o melhor conselho pode ser arredondar para valores integrais (na biblioteca C: round () e roundf (), ou seja, permanecer no formato FP) e depois comparar. O arredondamento para uma fração decimal específica resolve a maioria dos problemas com a saída.
Além disso, em problemas reais de processamento de números (os problemas para os quais o FP foi inventado em computadores antigos e terrivelmente caros), as constantes físicas do universo e todas as outras medições são conhecidas apenas por um número relativamente pequeno de números significativos; portanto, todo o espaço do problema era "inexato" de qualquer maneira. A "precisão" do FP não é um problema nesse tipo de aplicativo.
Todo o problema realmente surge quando as pessoas tentam usar o FP para a contagem de feijões. Funciona para isso, mas apenas se você se apegar a valores integrais, o que meio que anula o ponto de usá-lo. É por isso que temos todas essas bibliotecas de software de fração decimal.
Adoro a resposta da Pizza por Chris , porque ela descreve o problema real, não apenas a habitual sensação de "imprecisão". Se o FP fosse simplesmente "impreciso", poderíamos consertar isso e o teríamos feito décadas atrás. A razão pela qual não temos isso é porque o formato FP é compacto e rápido e é a melhor maneira de processar muitos números. Além disso, é um legado da era espacial e da corrida armamentista e tentativas iniciais de resolver grandes problemas com computadores muito lentos usando pequenos sistemas de memória. (Às vezes, núcleos magnéticos individuais para armazenamento de 1 bit, mas isso é outra história. )
Se você está apenas contando beans em um banco, as soluções de software que usam representações de string decimal em primeiro lugar funcionam perfeitamente. Mas você não pode fazer cromodinâmica quântica ou aerodinâmica dessa maneira.
fonte
nextafter()
com um incremento ou decréscimo inteiro na representação binária de um flutuador IEEE. Além disso, você pode comparar carros alegóricos como números inteiros e obter a resposta certa, exceto quando ambos são negativos (por causa da magnitude do sinal versus o complemento de 2).Você tentou a solução de fita adesiva?
Tente determinar quando os erros ocorrem e corrija-os com instruções if curtas, não é bonito, mas para alguns problemas é a única solução e essa é uma delas.
Eu tive o mesmo problema em um projeto de simulação científica em c # e posso dizer que, se você ignorar o efeito borboleta, ele se transformará em um grande dragão gordo e o morderá no a **
fonte
Para oferecer a melhor solução , posso dizer que descobri o seguinte método:
Deixe-me explicar por que é a melhor solução. Como outros mencionados nas respostas acima, é uma boa ideia usar a função Javascript toFixed () pronta para usar para resolver o problema. Mas provavelmente você encontrará alguns problemas.
Imagine que você está indo para somar dois números flutuar como
0.2
e0.7
aqui está:0.2 + 0.7 = 0.8999999999999999
.O resultado esperado foi
0.9
que significa que você precisa de um resultado com precisão de 1 dígito neste caso. Portanto, você deveria ter usado,(0.2 + 0.7).tofixed(1)
mas não pode simplesmente fornecer um determinado parâmetro para toFixed (), pois depende do número fornecido, por exemploNeste exemplo, você precisa de 2 dígitos de precisão
toFixed(2)
, portanto, qual deve ser o parâmetro para caber em cada número flutuante?Você pode dizer que seja 10 em todas as situações:
Droga! O que você vai fazer com esses zeros indesejados depois das 9? É hora de convertê-lo para flutuar para fazê-lo como você deseja:
Agora que você encontrou a solução, é melhor oferecê-la como uma função como esta:
Vamos tentar você mesmo:
Você pode usá-lo desta maneira:
Como o W3SCHOOLS sugere que também existe outra solução, você pode multiplicar e dividir para resolver o problema acima:
Lembre-se de que
(0.2 + 0.1) * 10 / 10
não funcionará, embora pareça o mesmo! Prefiro a primeira solução, pois posso aplicá-la como uma função que converte a flutuação de entrada em flutuante de saída precisa.fonte
Esses números estranhos aparecem porque os computadores usam o sistema de números binários (base 2) para fins de cálculo, enquanto usamos o decimal (base 10).
Há uma maioria de números fracionários que não podem ser representados precisamente em binário ou decimal ou em ambos. Resultado - Um número arredondado (mas preciso) resulta.
fonte
Muitas das numerosas duplicatas dessa pergunta perguntam sobre os efeitos do arredondamento de ponto flutuante em números específicos. Na prática, é mais fácil ter uma idéia de como isso funciona, observando os resultados exatos dos cálculos de interesse, e não apenas lendo sobre ele. Algumas linguagens fornecem maneiras de fazer isso - como converter um
float
oudouble
paraBigDecimal
em Java.Como essa é uma questão independente da linguagem, ela precisa de ferramentas independentes da linguagem, como um conversor de ponto decimal para ponto flutuante .
Aplicando-o aos números da pergunta, tratados como duplos:
0.1 converte para 0.1000000000000000055511151231257827021181583404541015625,
0,2 converte para 0,200000000000000011102230246251565404236316680908203125,
0,3 converte para 0,299999999999999988897769753748434595763683319091796875 e
0.30000000000000004 converte para 0.3000000000000000444089209850062616169452667236328125.
A adição manual dos dois primeiros números ou em uma calculadora decimal, como a Calculadora de precisão total , mostra que a soma exata das entradas reais é 0,3000000000000000166533453693773481063544750213623046875.
Se fosse arredondado para o equivalente a 0,3, o erro de arredondamento seria 0,0000000000000000277555756156289135105907917022705078125. O arredondamento para o equivalente a 0,30000000000000004 também fornece erro de arredondamento 0.0000000000000000277555756156289135105907917022705078125. O desempate de arredondamento para o par se aplica.
Retornando ao conversor de ponto flutuante, o hexadecimal bruto para 0,30000000000000004 é 3fd3333333333334, que termina em um dígito par e, portanto, é o resultado correto.
fonte
Dado que ninguém mencionou isso ...
Algumas linguagens de alto nível, como Python e Java, vêm com ferramentas para superar as limitações binárias de ponto flutuante. Por exemplo:
decimal
Módulo do Python eBigDecimal
classe do Java , que representam números internamente com notação decimal (em oposição à notação binária). Ambos têm precisão limitada, portanto ainda propensos a erros, mas resolvem os problemas mais comuns com aritmética binária de ponto flutuante.Os decimais são muito bons quando se lida com dinheiro: dez centavos mais vinte centavos são sempre exatamente trinta centavos:
O
decimal
módulo do Python é baseado no padrão IEEE 854-1987 .fractions
Módulo do Python eBigFraction
classe do Apache Common . Ambos representam números racionais como(numerator, denominator)
pares e podem fornecer resultados mais precisos que a aritmética decimal de ponto flutuante.Nenhuma dessas soluções é perfeita (especialmente se observarmos as performances ou se precisarmos de uma precisão muito alta), mas elas ainda resolvem um grande número de problemas com a aritmética binária de ponto flutuante.
fonte
Posso apenas adicionar; as pessoas sempre assumem que este é um problema do computador, mas se você contar com as mãos (base 10), não poderá obtê-
(1/3+1/3=2/3)=true
lo, a menos que tenha infinito para adicionar 0,333 ... a 0,333 ... assim como no(1/10+2/10)!==3/10
problema da base 2, você o trunca para 0,333 + 0,333 = 0,666 e provavelmente o arredonda para 0,667, o que também seria tecnicamente impreciso.Contar no ternário, e terços não são um problema - talvez alguma corrida com 15 dedos em cada mão pergunte por que sua matemática decimal foi quebrada ...
fonte
O tipo de matemática de ponto flutuante que pode ser implementada em um computador digital usa necessariamente uma aproximação dos números reais e operações neles. (A versão padrão tem mais de cinquenta páginas de documentação e tem um comitê para lidar com suas erratas e aperfeiçoamentos adicionais.)
Essa aproximação é uma mistura de aproximações de diferentes tipos, cada uma das quais pode ser ignorada ou cuidadosamente contabilizada devido à sua maneira específica de desvio da exatidão. Também envolve vários casos excepcionais explícitos, tanto no nível de hardware quanto de software, que a maioria das pessoas passa direto, fingindo não perceber.
Se você precisar de precisão infinita (usando o número π, por exemplo, em vez de um de seus muitos períodos mais curtos), escreva ou use um programa matemático simbólico.
Mas se você concorda com a idéia de que, às vezes, a matemática de ponto flutuante é confusa em valor, a lógica e os erros podem se acumular rapidamente, e você pode escrever seus requisitos e testes para permitir isso, seu código pode frequentemente se adaptar ao que está em sua FPU.
fonte
Apenas por diversão, brinquei com a representação de carros alegóricos, seguindo as definições do padrão C99 e escrevi o código abaixo.
O código imprime a representação binária de carros alegóricos em 3 grupos separados
e depois imprime uma soma que, quando somada com precisão suficiente, mostrará o valor que realmente existe no hardware.
Portanto, quando você escreve
float x = 999...
, o compilador transformará esse número em uma representação de bits impressa pela função, dexx
modo que a soma impressa pela funçãoyy
seja igual ao número fornecido.Na realidade, essa soma é apenas uma aproximação. Para o número 999.999.999, o compilador inserirá na representação em bits do número flutuante o número 1.000.000.000
Após o código, anexo uma sessão do console, na qual calculo a soma dos termos para as duas constantes (menos PI e 999999999) que realmente existem no hardware, inseridas pelo compilador.
Aqui está uma sessão de console em que eu calculo o valor real do flutuador que existe no hardware. Eu costumava
bc
imprimir a soma dos termos gerados pelo programa principal. Pode-se inserir essa soma em pythonrepl
ou algo semelhante também.É isso aí. O valor de 999999999 é de fato
Você também pode verificar se
bc
-3,14 também está perturbado. Não se esqueça de definir umscale
fatorbc
.A soma exibida é o que está dentro do hardware. O valor que você obtém calculando depende da escala que você definir. Eu defini o
scale
fator como 15. Matematicamente, com precisão infinita, parece que é 1.000.000.000.fonte
Outra maneira de ver isso: Utilizados são 64 bits para representar números. Como conseqüência, não há mais que 2 ** 64 = 18.446.744.073.709.551.616 números diferentes podem ser representados com precisão.
No entanto, Math diz que já existem infinitas casas decimais entre 0 e 1. O IEE 754 define uma codificação para usar esses 64 bits com eficiência para um espaço numérico muito maior, mais NaN e +/- Infinito, para que haja lacunas entre os números representados com precisão preenchidos com números apenas aproximados.
Infelizmente 0,3 fica em uma lacuna.
fonte
Imagine trabalhar na base dez com, digamos, 8 dígitos de precisão. Você verifica se
e saiba que isso retorna
false
. Por quê? Bem, como números reais, temos1/3 = 0,333 .... e 2/3 = 0,666 ....
Truncando em oito casas decimais, obtemos
que é, obviamente, diferente de
1.00000000
exatamente0.00000001
.A situação para números binários com um número fixo de bits é exatamente análoga. Como números reais, temos
1/10 = 0,0001100110011001100 ... (base 2)
e
1/5 = 0,0011001100110011001 ... (base 2)
Se os truncássemos para, digamos, sete bits, teríamos
enquanto, por outro lado,
3/10 = 0,01001100110011 ... (base 2)
que, truncado para sete bits, é
0.0100110
, e esses diferem exatamente0.0000001
.A situação exata é um pouco mais sutil, porque esses números geralmente são armazenados em notação científica. Então, por exemplo, em vez de armazenar 1/10,
0.0001100
podemos armazená-lo como algo parecido1.10011 * 2^-4
, dependendo de quantos bits alocamos para o expoente e a mantissa. Isso afeta quantos dígitos de precisão você obtém para seus cálculos.O resultado é que, devido a esses erros de arredondamento, você basicamente nunca deseja usar == em números de ponto flutuante. Em vez disso, você pode verificar se o valor absoluto da diferença é menor do que algum número pequeno fixo.
fonte
Desde o Python 3.5, você pode usar a
math.isclose()
função para testar a igualdade aproximada:fonte
Como esse segmento ramificou-se um pouco em uma discussão geral sobre as implementações atuais de ponto flutuante, eu acrescentaria que existem projetos para corrigir seus problemas.
Dê uma olhada em https://posithub.org/, por exemplo, que mostra um tipo de número chamado posit (e seu antecessor unum) que promete oferecer melhor precisão com menos bits. Se meu entendimento estiver correto, também corrigirá o tipo de problemas na pergunta. Projeto bastante interessante, a pessoa por trás disso é um matemático, Dr. John Gustafson . A coisa toda é de código aberto, com muitas implementações reais em C / C ++, Python, Julia e C # ( https://hastlayer.com/arithmetics ).
fonte
From https ://0.30000000000000004.com/
fonte
Os números decimais, tais como
0.1
,0.2
e0.3
não estão representados exatamente em binário codificado tipos de ponto flutuante. A soma das aproximações0.1
e0.2
difere da aproximação usada0.3
, daí a falsidade de0.1 + 0.2 == 0.3
como pode ser vista mais claramente aqui:Resultado:
Para que esses cálculos sejam avaliados com mais confiabilidade, você precisará usar uma representação baseada em decimal para valores de ponto flutuante. O Padrão C não especifica esses tipos por padrão, mas como uma extensão descrita em um relatório técnico .
Os
_Decimal32
,_Decimal64
e_Decimal128
tipos podem estar disponíveis no seu sistema (por exemplo, GCC suporta-los em alvos selecionados , mas Clang não apoiá-los no OS X ).fonte
Math.sum (javascript) .... tipo de substituição de operador
a ideia é usar os operadores Math, para evitar erros de flutuação
Math.sum detecta automaticamente a precisão a ser usada
Math.sum aceita qualquer número de argumentos
fonte
Acabei de ver esta questão interessante em torno de pontos flutuantes:
Considere os seguintes resultados:
Podemos ver claramente um ponto de interrupção quando
2**53+1
- tudo funciona bem até2**53
.Isso acontece devido ao formato de ponto flutuante binário de dupla precisão IEEE 754: binary64
Na página da Wikipedia para o formato de ponto flutuante de precisão dupla :
Obrigado a @a_guest por me indicar isso.
fonte
Uma pergunta diferente foi nomeada como duplicada para esta:
Em C ++, por que o resultado é
cout << x
diferente do valor que um depurador está mostrando parax
?O
x
na questão é umafloat
variável.Um exemplo seria
O depurador mostra
9.89999962
, a saída dacout
operação é9.9
.A resposta acaba sendo a
cout
precisão padrão parafloat
6, então arredonda para 6 dígitos decimais.Veja aqui para referência
fonte