Problema ao somar floats com JavaScript (1.75 + 2.68 + 5.56 == 9.989999999999998)

4,050 views
Skip to first unread message

Paulo Diovani

unread,
Dec 27, 2010, 7:37:21 AM12/27/10
to jque...@googlegroups.com
Pessoal, estou tendo um problema incomum, e ainda sequer descobri a causa.

Ao tentar somar 1.75 + 2.68 + 5.56 o resultado esperado é 9.99, contudo algumas engines (na prática, acontece no Firefox e no Google Chrome) me retornam 9.989999999999998.

Alguém sabe a razão disso, e um modo de contornar o problema?

__
Paulo Diovani Gonçalves
http://diovani.com

Ruan Carlos

unread,
Dec 27, 2010, 7:46:36 AM12/27/10
to jque...@googlegroups.com

Flavia Missi

unread,
Dec 27, 2010, 7:48:33 AM12/27/10
to jque...@googlegroups.com
Hey!

Na verdade, na minha opnião isso não é realmente um problema. Quando você obtêm 9.99 é porque o resultado já vem "arredondado". Olha só (em python):

>>> 1.75 + 2.68 + 5.56
9.9899999999999984

Tenho um artigo que fala disso, mas o link tá em casa. Se quiser te passo depois.

A solução é a do Ruan, arredondar. =)

Abraços,


2010/12/27 Paulo Diovani <pa...@diovani.com>



--
Flávia Missi
Software Developer

Thiago Santos

unread,
Dec 27, 2010, 7:55:06 AM12/27/10
to jque...@googlegroups.com
Ou seja, esqueça o que aprendeu na escola sobre 2+2=4

2+2 pode ser igual a 3,989999999999999999998

haha


Atc,
Thiago Santos
www.tigurio.com.br
______________________

MSN: tig...@gmail.com
GTalk: tig...@gmail.com
Twitter: twitter.com/tigurio
Skype: tigurio
Facebook: http://www.facebook.com/tigurio
________________________________________


2010/12/27 Flavia Missi <flavi...@gmail.com>

hnrq

unread,
Dec 27, 2010, 7:56:43 AM12/27/10
to jque...@googlegroups.com
Isso ai é ponto flutuante,  estavamos discutindo isso em uma lista php esses dias...

Dica:
http://docs.sun.com/source/806-3568/ncg_goldberg.html

:)

Henrique Lauro Bagio de Souza.

Washington Botelho

unread,
Dec 27, 2010, 8:16:03 AM12/27/10
to jque...@googlegroups.com
Oi Paulo,

Se você quiser arredondar como o Ruan te explicou, poderá usar o .toFixed(x) para facilitar as coisas: http://jsbin.com/esiqo5/2/edit

Mas se você precisa realmente de precisão e não simplesmente arredondamento em seu código, pesquise por "BigDecimal JavaScript".

Washington Botelho
http://wbotelhos.com.br
http://twitter.com/wbotelhos

Paulo Diovani

unread,
Dec 27, 2010, 8:55:51 AM12/27/10
to jque...@googlegroups.com
Agradeço, desde já, a ajuda de todos até aqui.

Bem, eu já imaginava que o problema (notem que falo "problema", ou seja, a situação que tenho em mãos, nunca considerei que fosse um erro) é causado pelo modo que uma CPU calcula pontos flutuantes.

Estou lendo o artigo no link que o Henrique mandou, o que já me esclareceu algumas coisas (embora matemática não seja o meu forte).

Flavia, se puder me enviar o artigo de que falou também será de grande ajuda.

Acontece que o problema é um pouco mais complicado do que parece. Vou detalhar o caso...

No sistema que estou trabalhando o usuário tem a opção de definir tanto a quantidade de casas decimais, como se o resultado do cálculo deve, ou não, ser arredondado.
Assim, no caso da soma 1.75 + 2.68 + 5.56, caso o usuário tenha decidido usar duas ou mais casas decimais, o resultado esperado será sempre 9.99, contudo, se ele decidir utilizar apenas uma, caso tenha decidido arredondar, o resultado esperado será 10, caso contrário, será 9.9.

A intenção a regra é como no algoritmo a seguir:

se (arredonda)
resultado = round(resultado)
senao
resultado = trunca(resultado, numero_decimais)
fimse


Assim, caso o usuário decida não arredondar, e utilizar duas casas demais, ele espera os seguintes resultados no cálculo a seguir:
(1.75 + 2.68 + 5.56) / 3 == 3.33
(1.75 + 2.68 + 5.55) / 3 == 3.32

...e caso decida arredondar, ele espera:
(1.75 + 2.68 + 5.56) / 3 == 3.33
(1.75 + 2.68 + 5.55) / 3 == 3.33


Irei procurar agora pelo termo "BigDecimal JavaScript" que o Washington sugeriu.

Flavia Missi

unread,
Dec 27, 2010, 10:31:03 AM12/27/10
to jque...@googlegroups.com
Oi Paulo!

Então, o link que eu disse que tinha é o mesmo que o Henrique mandou. ;)

Quanto ao seu problema, testei aqui e dá pra você usar a função toPrecision() passando 3 como parâmetro:

var a = 1.75 + 2.68 + 5.56;//9.989999999999998
a.toPrecision(3);//"9.99"

O problema desse método é que ele retorna uma string, mas dá pra dar um parseFloat() pra voltar pra float. Além disso, ele só retorna um dos valores que você precisa pq pelo que percebí ele sempre arredonda para cima.. :(

Enfim, nem sei se esse é o melhor método, pode ser uma puta gambi rsrsrs. Foi a única solução que encontrei.

Espero ter ajudado. :)

2010/12/27 Paulo Diovani <pa...@diovani.com>



--

Paulo Diovani Gonçalves

unread,
Dec 27, 2010, 10:53:57 AM12/27/10
to jque...@googlegroups.com
Obrigado, Flavia, mas o método toPrecision() não é a solução.

Já utilizo toFixed() que define o número de casas decimais e arredonda o valor. Mas como falei, o problema é que 
  1. O usuário pode optar por não arredondar, caso em que utilizo uma solução como a que o Ruan propôs porém usando Mathfloor().
  2. Mesmo que o usuário opte por não arredondar, eu preciso encontrar um método de garantir que a soma no assunto dê 9.99.
Minha intenção é descobrir quais fatores que fazem com que a soma não dê o resultado esperado, para fazer a correção necessária sem realmente arredondar o valor.

Uma solução que estou cogitando é arredondar o resultado prévio utilizando uma casa decimal além do que o usuário deseja. Assim, meu resultado ficaria como 9.990, no caso, e 3.330 usando a divisão.

Math.round((1.75+2.68+5.57) / 3 * Math.pow(10,3)) / Math.pow(10,3)

Ruan Carlos

unread,
Dec 27, 2010, 11:01:22 AM12/27/10
to jque...@googlegroups.com
http://jsbin.com/ixuzo3/edit

-----------------------------------------------------------------
Ruan Carlos
@ruanltbg
www.ruancarlos.com.br
Bacharel em Sistemas de Informação - Desenvolvedor web - Oracle WebCenter Certified



Paulo Diovani

unread,
Dec 27, 2010, 11:39:00 AM12/27/10
to jque...@googlegroups.com

Ruan, toPrecision não é aceitável porque ele define o número total de dígitos, e eu posso ter qualquer número de dígitos a esquerda da vírgula.

 

Reparei agora que o arredondamento por parte do usuário só será necessário nos cálculos em que houverem divisão. Assim não tem problema eu apenas arredondar o resultado da soma previamente.

 

Só estou pensando agora se uso toFixed() ou se uso um Math.round() juntamente com uma potência de 10 (como o Ruan sugeriu previamente):

 

 

parseFloat( soma.toFixed(2) )

 

ou

 

Math.round(soma * Math.pow(10,2)) / Math.pow(10,2)

 

 

Vou fazer mais alguns testes e optar pela solução mais rápida.

 

Obrigado a todos pela ajuda.

Paulo Diovani

unread,
Dec 30, 2010, 12:23:14 PM12/30/10
to jque...@googlegroups.com
Só para finalizar a thread...

Criei um método para calcular decimais evitando o erro acima.

http://jsbin.com/iwine4/edit

O método utiliza uma solução baseada na primeira proposta do Ruan, mas ao invés de arredondar (pois a idéia é justamente evitar o arredondamento) eu transformo o número em inteiro multiplicando por uma potência de 10, e divido o resultado posteriormente pela mesma potência.


Exemplo de uso:

var result = 1.75.sumFloat( 2.68 ).sumFloat( 5.56 );

Reply all
Reply to author
Forward
0 new messages