Executando verificação de segurança...
5

Na maioria das (senão em todas as) linguagens mainstream, os números de ponto flutuante seguem a norma IEEE 754.

Este padrão possui uma série de problemas de imprecisão, por causa da forma como foi definido. No caso de um double, o formato é:

  • 1 bit para o sinal
  • expoente: inteiro de 11 bits, para indicar o valor do expoente deslocado 1023 unidades, ou então um dos dois valores com significados especiais: 0x000 para valores subnormais e zero; 0x7FF para representar infinito e NaN. (i.e. 2x - 1023, onde x é o valor inteiro do campo)
  • mantissa: 52 bits, para valores normais do expoente, representa um valor racional que vai de 1.0 inclusive até 2.0 exclusive, matematicamente [1, 2[. Para o valor do expoente 0x000, então representa valores subnormais (i.e. menores que o menor valor normal representável) ou zero; Para o valor do expoente 0x7FF representa infinito se for 0, ou NaN se for diferente de 0.

O problema é que a norma IEEE 754 tenta representar as frações na base 2, e isso não é suficiente para representar todos os valores possíveis na base 10. O máximo que ele pode fazer são aproximações.

Por exemplo, o número 0.1 na base decimal é representado como 1 x 10-1. Mas na base 2, não tem como representá-lo com exatidão. Se usarmos uma casa decimal, (0.1, que seria 1 x 2-1) o resultado equivale a 0.5 em decimal (nada próximo de 0.1). Se usarmos 8 casas decimais, ficaria 1.10011001 x 2-4, que equivale a 0.099853515625 em decimal. Mais próximo de 0.1, mas ainda não é exato. Mesmo se usarmos 23 casas decimais, teremos 1.10011001100110011001101 × 2-4, que é equivalente a 0.100000001490116119384765625 em decimal. Próximo de 0.1, mas ainda sim inexato.

E não adianta, por mais que adicionemos casas decimais na base 2, o máximo que conseguimos é aproximar um pouco mais o valor. Mas muitos valores jamais poderão ser representados com exatidão.

Não tem jeito de "resolver", é algo intrínseco à norma. Se quer mais precisão, precisa usar bibliotecas dedicadas. Muitas linguagens possuem tipos como Decimal, BigDecimal, etc. Outras não têm, e só é possível com bibliotecas externas.

Para saber mais, veja aqui, aqui e aqui.


Como vc mencionou Python, uma alternativa é usar o módulo decimal, feito justamente para contornar os problemas de precisão do float:

from decimal import Decimal as d

print(d('128.23') - (d('118.23') + d('10'))) # 0.00

Em PHP e JavaScript não tem nada nativo, então somente com bibliotecas externas.

Carregando publicação patrocinada...
1