2

I am working on a piece of code where numerical equality is an important factor in several logical conditional. Clojure is doing something I dont know enough about to explain. For example:

user=> (- 5 4.9)
0.09999999999999964
user=> (- 5 4.8)
0.20000000000000018
user=> (- 5 2.9)
2.1
user=> (- 5 2.7)
2.3
user=> (- 5 2.8)
2.2
user=> (- 9 6.9)
2.0999999999999996 
user=> (- 9 2.9)
6.1

It seems like in some circumstance, Clojure numerics understands subtraction to 0.1, in other cases not. What is going on here?

David Williams
  • 8,388
  • 23
  • 83
  • 171

2 Answers2

6

By default these results are expressed as decimals, with the inherent rounding errors. You can switch to using BigDecimals by suffixing with the letter M:

user=> (- 5M 4.9M)
0.1M
user=> (- 5M 4.8M)
0.2M
user=> (- 9M 6.9M)
2.1M
user=> (- 9M 2.9M)
6.1M

To do the same with a variable that's a decimal, use the bigdec form:

user=> (def k 4.9)
#'user/k
user=> (- 5M k)
0.09999999999999964
user=> (- 5M (bigdec k))
0.1M
uselpa
  • 18,732
  • 2
  • 34
  • 52
  • How do I do this if these numbers are stored in variables, coming from somewhere else? – David Williams May 28 '13 at 20:45
  • Probably not of much use when the numbers are coming from elsewhere but if you're interested in accuracy you should also be aware of Clojure's ratios: (- 5 49/10) => 1/10 – status203 May 29 '13 at 07:39
4

Adding some Ms to the number literals in question addresses the symptoms, but does nothing to improve David's understanding of the fundamentals of floating-point math. See the classic What Every Computer Scientist Should Know About Floating-Point Arithmetic, or any one of the numerous questions on SO about floating-point inexactness. Here are two examples I could easily find; I've seen at least three for Clojure alone, but it's hard to find them with a search.

Community
  • 1
  • 1
amalloy
  • 89,153
  • 8
  • 140
  • 205
  • But "adding some Ms" is the only solution to his problem, or can you see any other solution? – uselpa May 29 '13 at 16:02
  • There are loads of good solutions. For example, instead of working with decimals like 4.5, work with rationals like 9/20, which have infinite precision. Or multiply everything by 10 and work with integers like 45, which have sufficient precision. Or instead of relying on strict equality, use "good enough" comparisons with an epsilon. Using bigdecimals does not completely solve the problem, because you can't represent numbers like 1/3 as a bigdecimal at all. (of course, my multiplication+integers solution has the same problem) – amalloy May 29 '13 at 19:32