Pages

Sunday, January 17, 2010

Continuing the BigDecimal's Game: Scala vs Clojure

After studying the issues with decimal types in Groovy and JRuby, with lafy, we discovered some funny things in Scala and Clojure as well.


Consider, if you need to accumulate 0.1 until the result is one. Groovy does it as expected


0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 = 1.0


Just because 0.1 is BigDecimal in Groovy.


In Clojure, the example is not that nice:


(+ 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1) results in 0.9999999999999999.


Basically, the problem is solved when you add the M qualifier to each of the numbers in the expression, like 


(+ 0.1M 0.1M 0.1M 0.1M 0.1M 0.1M 0.1M 0.1M 0.1M 0.1M) = 1.0


Or use rational numbers:

(+ 1/10 1/10 1/10 1/10 1/10 1/10 1/10 1/10 1/10 1/10) = 1.0


The hypothetical problem is that, if a number (say, some money amount) is stored in the database, and the calculation is configured by the user in business rules via some DSL, then a user could create a calculation that   could lead to a loss of precision. For instance:


(* 100M 1.1) = 110.00000000000001
(class (* 100M 1.1)) = java.lang.Double


Double there is!!!


The same kind calculation gives BigDecimal as a result in Scala:


scala.BigDecimal(1.5) * 1.5
BigDecimal = 2.25


The question is what is the rationale behind such behavior and which result is correct? To me, when writing an application which operates with monetary amounts, it is the Scala which is correct.





5 comments:

Lauri said...

Why don't You just add the M qualifier to the numbers in the expression?
Automating this shouldn't be too hard.. :)

Anton Arhipov said...

Lauri,

this is not hard and it is not a problem at all.

The interesting thing is how the types are converted in the expressions. In Scala and Clojure this is quite different.

Lauri said...

I haven't looked at Scala but in Clojure there is the following rule; any numeric operation involving Doubles yields a Double (please read other rules at http://clojure.org/data_structures#toc2).
This is expected because Doubles are interpreted as approximations.
There is also a quite recent conversation about this topic on Clojure user group:
http://groups.google.com/group/clojure/browse_thread/thread/95afe60baa55d4ec

Anton Arhipov said...

Lauri,

I'm aware about that rule and I know about the discussion on the group because my friend Jevgeni (aka lafy) started that :)

I'm not arguing that it is wrong but I insist that it is *bad* coz when someone is counting money it will eventually become a source of bugs in the software. Not because the language is wrong but because of the human nature :)

Jevgeni Holodkov said...

The very last suggestion on that google group is the one I like the most. Add params to the Clojure runtime to read all decimals either as double (now) or as ratios (wanted). Shouldn't be so hard to implement, I think.

Disqus for Code Impossible