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

As linguagens de programação não conseguem fazer esse simples calculo! Veja

Alguém consegue me explicar o porquê isso acontece?


print(128.23 - (118.23 + 10)

echo 128.23 - (118.23 + 10);
 console.log(128.23 - (118.23 + 10.00))

Linguaguens usadas para teste: PHP, Python, JavaScript

Ao invés de retornar 0 ou até 0.00, estão retornando -2.842170943040401e-14

Carregando publicação patrocinada...
6

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.

1
2

O computador não lida muito bem com ponto flutuante (números reais, fracionados).
Esse resultado estranho, se não me engando, é notação científica, é uma aproximação do resultado encontrado: -2.842170943040401 elevado a -14

1
1

tem várias técnicas diferentes que a gente pode usar, algumas linguagens tem libs ou funções que auxiliam nisso (aí tem que vasculhar as documentações). no meu caso, das soluções que testei, a que mais gostei e tive melhor resultado foi trabalhar com inteiros, e é a mesma coisa pra qualquer linguagem

1

Se quer trabalhar com valores exatos, pode usar outras libs específicas (como já mencionei na outra resposta).

Mas se for para continuar com float, o que vc pode fazer é verificar se o valor está próximo de zero, com alguma tolerância.

Em Python, por exemplo, pode usar math.isclose:

from math import isclose

x = 128.23 - (118.23 + 10)
if isclose(x, 0, rel_tol=1e-10, abs_tol=1e-10):
    print('ok') # entra no if

Os parâmetros rel_tol e abs_tol são a tolerância que vc usa para comparar os números (consulte a documentação para mais detalhes). Não tem muito jeito, tem que ficar ajustando os valores para a precisão que você vai trabalhar.

Em JavaScript, pode ser feito algo similar. Uma versão bem simplificada seria:

function iguais(a, b, tolerancia=1e-10) {
    return Math.abs(a - b) <= tolerancia;
}

var x = 128.23 - (118.23 + 10.00);
console.log(iguais(x, 0)); // true
1

Fala samuhmatos!

Testando apenas a operação dentro dos parêntesis
>>> 118.23+10

o resultado obtido é: 128.23000000000002

Aplicando um round na operação:

>>> 128.23 - (round(118.23 + 10,2))

o resultado obtido é 0.0

Mas o resultado incorreto provém realmente da norma aplicada para ponto flutuante conforme o kht explicou muito bem!

Conteúdo excluído
1

Só pra explicar: segundo a documentação, em Oracle um literal numérico é do tipo NUMBER, que é um tipo que não tem os problemas de imprecisão dos números de ponto flutuante. Por isso o cálculo fica exato.

Mas só como curiosidade, se você colocar o sufixo d, os números passam a ser tipos de ponto flutuante, e aí os problemas de imprecisão aparecem:

SELECT to_char(128.23d - (118.23d + 10.00d), '9.9999999999999999999') result  FROM dual;

Formatei com várias casas decimais, senão ele só ia mostrar -0. Resultado:

                RESULT
----------------------
 -.0000000000000284217

Só pra mostrar a diferença, veja sem o sufixo d:

SELECT to_char(128.23 - (118.23 + 10.00), '9.9999999999999999999') result  FROM dual;

Resultado:

                RESULT
----------------------
  .0000000000000000000
Conteúdo excluído
1

Eu preciso identidicar se o valor é 0 para dar processeguimento na função. Estes valores são convertidos em números, mas são valores de dinheiro.

Preciso identificar se resta algum valor, e ai se for 0 pode dar prosseguimento

1

Se está trabalhando com valores monetários, então com certeza float não é a melhor opção, por causa dos problemas de precisão que já mencionei.

Nesse caso o ideal é usar tipos que não tem esse problema (também já citados na outra resposta), como o decimal do Python (em PHP e JavaScript, procure por bibliotecas como "BigDecimal", ou "Money", tem várias).

Também existe a abordagem de trabalhar sempre com inteiros, representando a quantidade de centavos, como o @natanael755 já mencionou.

Independente da solução escolhida, lembre-se que valores monetários tem outros detalhes para tratar, como por exemplo regras de arredondamento (que varia muito conforme o contexto), etc (e bibliotecas dedicadas como o decimal do Python fornecem amplo suporte a essas coisas).