Considere o seguinte código:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
Por que essas imprecisões acontecem?
Considere o seguinte código:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
Por que essas imprecisões acontecem?
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ão binary64
, a representação pode ser escrita exatamente como
0.1000000000000000055511151231257827021181583404541015625
em decimal ou0x1.999999999999ap-4
na notação hexadecimal C99 .Por outro lado, o número racional 0.1
, ou seja 1/10
, pode ser escrito exatamente como
0.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
e 0.3
no seu programa também serão aproximações aos seus valores verdadeiros. Acontece que o mais próximo double
que 0.2
é maior do que o número racional 0.2
, mas que o mais próximo double
que 0.3
é menor do que o número racional 0.3
. A soma de 0.1
e 0.2
acaba sendo maior que o número racional 0.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 fazerif (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.
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.
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.
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.
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, onde k>=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) seria 2+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.
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.
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.
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.
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.
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.
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.
Além das outras respostas corretas, convém dimensionar seus valores para evitar problemas com a aritmética de ponto flutuante.
Por exemplo:
var result = 1.0 + 2.0; // result === 3.0 returns true
... ao invés de:
var result = 0.1 + 0.2; // result === 0.3 returns false
A expressão 0.1 + 0.2 === 0.3
retorna false
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 de 25.50
dólares.
1 Douglas Crockford: JavaScript: As peças boas : Apêndice A - Peças horríveis (página 105) .
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
valor = (-1) ^ s * (1.m 51 m 50 ... m 2 m 1 m 0 ) 2 * 2 e-1023
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 em 1
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 ( :
) :
public static string BinaryRepresentation(double value)
{
long valueInLongType = BitConverter.DoubleToInt64Bits(value);
string bits = Convert.ToString(valueInLongType, 2);
string leadingZeros = new string('0', 64 - bits.Length);
string binaryRepresentation = leadingZeros + bits;
string sign = binaryRepresentation[0].ToString();
string exponent = binaryRepresentation.Substring(1, 11);
string mantissa = binaryRepresentation.Substring(12);
return string.Format("{0}:{1}:{2}", sign, exponent, mantissa);
}
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:
0.1 => 0:01111111011:1001100110011001100110011001100110011001100110011010
0.2 => 0:01111111100:1001100110011001100110011001100110011001100110011010
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:
0.1 => 2^-4 * [1].1001100110011001100110011001100110011001100110011010
0.2 => 2^-3 * [1].1001100110011001100110011001100110011001100110011010
or
0.1 => 2^-56 * 7205759403792794 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125
Para adicionar dois números, o expoente precisa ser o mesmo, ou seja:
0.1 => 2^-3 * 0.1100110011001100110011001100110011001100110011001101(0)
0.2 => 2^-3 * 1.1001100110011001100110011001100110011001100110011010
sum = 2^-3 * 10.0110011001100110011001100110011001100110011001100111
or
0.1 => 2^-55 * 3602879701896397 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125
sum = 2^-55 * 10808639105689191 = 0.3000000000000000166533453693773481063544750213623046875
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:
sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1)
= 2^-54 * 5404319552844595.5 = 0.3000000000000000166533453693773481063544750213623046875
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.
a = 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
= 2^-2 * 1.0011001100110011001100110011001100110011001100110011
x = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1)
b = 2^-2 * 1.0011001100110011001100110011001100110011001100110100
= 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125
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 é:
sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110100
= 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125
considerando que a representação binária de 0,3 é:
0.3 => 2^-2 * 1.0011001100110011001100110011001100110011001100110011
= 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
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-a 0.3
, isto é (coloquei os bits distintos entre colchetes):
0.1 + 0.2 => 0:01111111101:0011001100110011001100110011001100110011001100110[100]
0.3 => 0:01111111101:0011001100110011001100110011001100110011001100110[011]
Convertidos de volta para decimal, esses valores são:
0.1 + 0.2 => 0.300000000000000044408920985006...
0.3 => 0.299999999999999988897769753748...
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
daria 0.3
: os poucos bits finais são arredondados.
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
seria 1 x 10⁻¹
, 0.2
seria 2 x 10⁻¹
e 0.3
seria 3 x 10⁻¹
. A matemática de números inteiros é fácil e exata, portanto a adição 0.1 + 0.2
obviamente resultará em 0.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⁻¹
e 0.25
é 1 x 2⁻²
, e adicioná-los em 3 x 2⁻²
, ou 0.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 de 0.2
é 7205759403792794 x 2⁻⁵⁵
; adicionando-os resulta em 10808639105689191 x 2⁻⁵⁵
, ou um valor decimal exato de 0.3000000000000000444089209850062616169452667236328125
. Os números de ponto flutuante geralmente são arredondados para exibição.
Erro de arredondamento do ponto flutuante. Do que todo cientista da computação deve saber sobre aritmética de ponto flutuante :
Comprimir infinitamente muitos números reais em um número finito de bits requer uma representação aproximada. Embora existam infinitos números inteiros, na maioria dos programas o resultado de cálculos inteiros pode ser armazenado em 32 bits. Por outro lado, dado qualquer número fixo de bits, a maioria dos cálculos com números reais produzirá quantidades que não podem ser representadas exatamente usando tantos bits. Portanto, o resultado de um cálculo de ponto flutuante geralmente deve ser arredondado para se ajustar novamente à sua representação finita. Esse erro de arredondamento é o recurso característico da computação em ponto flutuante.
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
e DBL_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.
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:
0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)
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:
0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)
* 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%).
Sumário
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.
Como isso aconteceu?
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.
Lidando com isso
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. )
Conclusão
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.
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.
if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
else { return n * 0.1 + 0.000000000000001 ;}
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 **
Para oferecer a melhor solução , posso dizer que descobri o seguinte método:
parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3
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
e 0.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 exemplo
`0.22 + 0.7 = 0.9199999999999999`
Neste 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:
(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"
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:
parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9
Agora que você encontrou a solução, é melhor oferecê-la como uma função como esta:
function floatify(number){
return parseFloat((number).toFixed(10));
}
Vamos tentar você mesmo:
function floatify(number){
return parseFloat((number).toFixed(10));
}
function addUp(){
var number1 = +$("#number1").val();
var number2 = +$("#number2").val();
var unexpectedResult = number1 + number2;
var expectedResult = floatify(number1 + number2);
$("#unexpectedResult").text(unexpectedResult);
$("#expectedResult").text(expectedResult);
}
addUp();
input{
width: 50px;
}
#expectedResult{
color: green;
}
#unexpectedResult{
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="number1" value="0.2" onclick="addUp()" onkeyup="addUp()"/> +
<input id="number2" value="0.7" onclick="addUp()" onkeyup="addUp()"/> =
<p>Expected Result: <span id="expectedResult"></span></p>
<p>Unexpected Result: <span id="unexpectedResult"></span></p>
Você pode usá-lo desta maneira:
var x = 0.2 + 0.7;
floatify(x); => Result: 0.9
Como o W3SCHOOLS sugere que também existe outra solução, você pode multiplicar e dividir para resolver o problema acima:
var x = (0.2 * 10 + 0.1 * 10) / 10; // x will be 0.3
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.
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.
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
ou double
para BigDecimal
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.
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 e BigDecimal
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:
>>> 0.1 + 0.2 == 0.3
False
>>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
True
O decimal
módulo do Python é baseado no padrão IEEE 854-1987 .
fractions
Módulo do Python e BigFraction
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.
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 ...
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.
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
SIGN EXPONENT FRACTION
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, de xx
modo que a soma impressa pela função yy
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.
#include <stdio.h>
#include <limits.h>
void
xx(float *x)
{
unsigned char i = sizeof(*x)*CHAR_BIT-1;
do {
switch (i) {
case 31:
printf("sign:");
break;
case 30:
printf("exponent:");
break;
case 23:
printf("fraction:");
break;
}
char b=(*(unsigned long long*)x&((unsigned long long)1<<i))!=0;
printf("%d ", b);
} while (i--);
printf("\n");
}
void
yy(float a)
{
int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));
int fraction = ((1<<23)-1)&(*(int*)&a);
int exponent = (255&((*(int*)&a)>>23))-127;
printf(sign?"positive" " ( 1+":"negative" " ( 1+");
unsigned int i = 1<<22;
unsigned int j = 1;
do {
char b=(fraction&i)!=0;
b&&(printf("1/(%d) %c", 1<<j, (fraction&(i-1))?'+':')' ), 0);
} while (j++, i>>=1);
printf("*2^%d", exponent);
printf("\n");
}
void
main()
{
float x=-3.14;
float y=999999999;
printf("%lu\n", sizeof(x));
xx(&x);
xx(&y);
yy(x);
yy(y);
}
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 python repl
ou algo semelhante também.
-- .../terra1/stub
@ qemacs f.c
-- .../terra1/stub
@ gcc f.c
-- .../terra1/stub
@ ./a.out
sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1
sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0
negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1
positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
-- .../terra1/stub
@ bc
scale=15
( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
999999999.999999446351872
É isso aí. O valor de 999999999 é de fato
999999999.999999446351872
Você também pode verificar se bc
-3,14 também está perturbado. Não se esqueça de definir um scale
fator bc
.
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.
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.
Imagine trabalhar na base dez com, digamos, 8 dígitos de precisão. Você verifica se
1/3 + 2 / 3 == 1
e saiba que isso retorna false
. Por quê? Bem, como números reais, temos
1/3 = 0,333 .... e 2/3 = 0,666 ....
Truncando em oito casas decimais, obtemos
0.33333333 + 0.66666666 = 0.99999999
que é, obviamente, diferente de 1.00000000
exatamente 0.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
0.0001100 + 0.0011001 = 0.0100101
enquanto, por outro lado,
3/10 = 0,01001100110011 ... (base 2)
que, truncado para sete bits, é 0.0100110
, e esses diferem exatamente 0.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 parecido 1.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.
Desde o Python 3.5, você pode usar a math.isclose()
função para testar a igualdade aproximada:
>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
>>> 0.1 + 0.2 == 0.3
False
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 ).
Na verdade, é bem simples. Quando você tem um sistema de base 10 (como o nosso), ele pode expressar apenas frações que usam um fator primordial da base. Os fatores primos de 10 são 2 e 5. Portanto, 1/2, 1/4, 1/5, 1/8 e 1/10 podem ser expressos de maneira limpa, porque todos os denominadores usam fatores primos de 10. Em contraste, 1 / 3, 1/6 e 1/7 são decimais repetidos porque seus denominadores usam um fator primo de 3 ou 7. Em binário (ou base 2), o único fator primo é 2. Portanto, você só pode expressar frações de maneira limpa que contém apenas 2 como fator primordial. Em binário, 1/2, 1/4, 1/8 seriam todos expressos de maneira limpa como decimais. Enquanto 1/5 ou 1/10 estaria repetindo decimais. Portanto, 0,1 e 0,2 (1/10 e 1/5), enquanto decimais limpos em um sistema base 10, estão repetindo decimais no sistema base 2 em que o computador está operando. Quando você faz contas com esses decimais repetidos,
Os números decimais, tais como 0.1
, 0.2
e 0.3
não estão representados exatamente em binário codificado tipos de ponto flutuante. A soma das aproximações 0.1
e 0.2
difere da aproximação usada 0.3
, daí a falsidade de0.1 + 0.2 == 0.3
como pode ser vista mais claramente aqui:
#include <stdio.h>
int main() {
printf("0.1 + 0.2 == 0.3 is %s\n", 0.1 + 0.2 == 0.3 ? "true" : "false");
printf("0.1 is %.23f\n", 0.1);
printf("0.2 is %.23f\n", 0.2);
printf("0.1 + 0.2 is %.23f\n", 0.1 + 0.2);
printf("0.3 is %.23f\n", 0.3);
printf("0.3 - (0.1 + 0.2) is %g\n", 0.3 - (0.1 + 0.2));
return 0;
}
Resultado:
0.1 + 0.2 == 0.3 is false
0.1 is 0.10000000000000000555112
0.2 is 0.20000000000000001110223
0.1 + 0.2 is 0.30000000000000004440892
0.3 is 0.29999999999999998889777
0.3 - (0.1 + 0.2) is -5.55112e-17
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 ).
Math.sum (javascript) .... tipo de substituição de operador
.1 + .0001 + -.1 --> 0.00010000000000000286
Math.sum(.1 , .0001, -.1) --> 0.0001
Object.defineProperties(Math, {
sign: {
value: function (x) {
return x ? x < 0 ? -1 : 1 : 0;
}
},
precision: {
value: function (value, precision, type) {
var v = parseFloat(value),
p = Math.max(precision, 0) || 0,
t = type || 'round';
return (Math[t](v * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p);
}
},
scientific_to_num: { // this is from https://gist.github.com/jiggzson
value: function (num) {
//if the number is in scientific notation remove it
if (/e/i.test(num)) {
var zero = '0',
parts = String(num).toLowerCase().split('e'), //split into coeff and exponent
e = parts.pop(), //store the exponential part
l = Math.abs(e), //get the number of zeros
sign = e / l,
coeff_array = parts[0].split('.');
if (sign === -1) {
num = zero + '.' + new Array(l).join(zero) + coeff_array.join('');
} else {
var dec = coeff_array[1];
if (dec)
l = l - dec.length;
num = coeff_array.join('') + new Array(l + 1).join(zero);
}
}
return num;
}
}
get_precision: {
value: function (number) {
var arr = Math.scientific_to_num((number + "")).split(".");
return arr[1] ? arr[1].length : 0;
}
},
sum: {
value: function () {
var prec = 0, sum = 0;
for (var i = 0; i < arguments.length; i++) {
prec = this.max(prec, this.get_precision(arguments[i]));
sum += +arguments[i]; // force float to convert strings to number
}
return Math.precision(sum, prec);
}
}
});
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
Considere os seguintes resultados:
error = (2**53+1) - int(float(2**53+1))
>>> (2**53+1) - int(float(2**53+1))
1
Podemos ver claramente um ponto de interrupção quando 2**53+1
- tudo funciona bem até 2**53
.
>>> (2**53) - int(float(2**53))
0
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 :
O ponto flutuante binário de precisão dupla é um formato comumente usado em PCs, devido à sua maior variedade sobre o ponto flutuante de precisão única, apesar de seu desempenho e custo de largura de banda. Como no formato de ponto flutuante de precisão única, ele não possui precisão em números inteiros quando comparado com um formato inteiro do mesmo tamanho. É comumente conhecido simplesmente como duplo. O padrão IEEE 754 especifica um binary64 como tendo:
- Bit de sinal: 1 bit
- Expoente: 11 bits
- Precisão significativa: 53 bits (52 armazenados explicitamente)
O valor real assumido por um dado de dupla precisão de 64 bits com um determinado expoente tendencioso e uma fração de 52 bits é
ou
Obrigado a @a_guest por me indicar isso.
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
float x = 9.9F;
O depurador mostra 9.89999962
, a saída da cout
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