On Mar 5, 2009, at 8:00 PM, mike.farn...@gmail.com wrote:

>
> I thought it neat that clojure supports fractions (just like
> Smalltalk).
> user=> (/ 3)
> 1/3
>
> I was sort of surprised by this.
>
> user=> (+ (float (* (/ 2) (/ 3))) (float (* (/ 2) (/ 3))) )
> 0.33333334

It comes as no surprise that certain numbers cannot be represented by  
a finite string of decimal digits. We all realize that 1/3 =  
0.3333..., and the "..." part is critical. Take it away and the  
equality goes away too. In other words, if that string of 3's stops,  
then we don't have 1/3 anymore.

This situation is true inside the computer too. The floating-point  
representation of 1/3 is binary 0.01010101010101... Whereas in  
decimal we have 1/3 = 3*1/10 + 3*1/100 + 3*1/1000 + ..., in binary we  
have 1/3 = 0*1/2 + 1*1/4 + 0*1/8 + 1*1/16 + ... If this sum stops we  
once again lose equality. However, the computer only has finite  
memory in which to represent a floating-point number, so the string  
of "01"s does in fact stop. The computer cannot represent 1/3 exactly  
in floating-point. Suppose for the sake of argument that the computer  
only stores the equivalent of 4 decimal digits. So we get 1/3 ->  
0.3333. But the true value is 0.3333 < 1/3 < 0.3334. In terms of the  
binary representation, depending on how many bits are used the  
decimal representation may be slightly closer to 0.3334 rather than  
0.3333. You saw that by using the "float" function, which coerces the  
Ratio 1/3 to a single-precision float:
(float 1/3) => 0.33333334

If you had coerced to a double-precision float you would have seen this:
(double 1/3) => 0.3333333333333333

Neither one of these is more "correct" per se (the double is more  
precise of course). They simply reflect the limitations of different  
finite representations of 1/3.

You can explore on your own by using Clojure's "rationalize" function  
(which as far as I can tell mirrors Common Lisp's RATIONAL function  
rather than its RATIONALIZE function):
(rationalize (float 1/3)) => 416666679084301/1250000000000000
(rationalize (double 1/3)) => 3333333333333333/10000000000000000

How close are these to the true values?
(- (rationalize (float 1/3)) 1/3) => 37252903/3750000000000000
(- 1/3 (rationalize (double 1/3))) => 1/30000000000000000

So the single-precision representation is slightly more than 1/3,  
which is why you see the terminal 4.

Here's a little program to help you look inside 1/3. Just type  
(dec2bin/display-one-third <PRECISION>) where <PRECISION> is either  
float or double (you will need to widen your terminal window):
(ns dec2bin
   (:use clojure.contrib.math))

(defn integer-to-binary [i]
   (if (zero? i)
     "0"
     (loop [i i
            result ""]
       (if (zero? i)
         (apply str result)
         (recur (quot i 2)
                (if (zero? (rem i 2))
                  (cons \0 result)
                  (cons \1 result)))) )))

(defn fraction-to-binary [x digits]
   (loop [n digits
          fraction x
          result ""]
     (if (zero? n)
       (apply str (reverse result))
       (let [whole (quot (* 2 fraction) 1)
             fraction (rem (* 2 fraction) 1)]
         (recur (dec n) fraction (cons (char (+ whole (int \0)))  
result)))) ))

(defn decimal-to-binary
   ([x] (decimal-to-binary x 10))
   ([x digits]
      (let [whole (integer-to-binary (quot x 1))
            fraction (fraction-to-binary (rem x 1) digits)]
        (format "%s.%s" whole fraction))))

(defn display-one-third [f]
   (prn (format "%5s %-20s %-40s %s" "bits" "floating-point"  
"fraction" "binary"))
   (let [precision (if (= f float) 16 32)]
     (dotimes [i precision]
       (let [j (inc i)
             sum (reduce + (map #(/ (expt 2 (* 2 %))) (range 1 (inc  
j))))]
         (prn (format "%5d %-20s %-40s %s" (* 2 j) (str (f sum)) (str  
sum) (decimal-to-binary sum (* 2 j)))) ))))


>
>  (=  (+ (float (* (/ 2) (/ 3))) (float (* (/ 2) (/ 3))) ) (/ 3) )
> yields true
>
>

You have to be very careful comparing floating-point numbers. While  
the above discussion of 1/3 should not be surprising, many people do  
get surprised when they look at other numbers in floating-point. A  
perfect example is 0.1. In decimal, this looks like a "nice" fraction  
that terminates after 1 decimal place. However, binary fractions are  
not represented using powers of 10: 1/10, 1/100, 1/1000, etc. We have  
to represent 0.1 in terms of: 1/2, 1/4, 1/8, etc. The binary  
representation of 0.1 is:
0.00011001100110011001100110011001100110011001100110011...
Again this is an infinite string represented in a finite amount of  
memory, so 0.1 is not exact. That's why you get this behavior:
(loop [i 1
        x 0.1]
   (prn (format "%d %s %s" i (str x) (str (* i 0.1))))
   (when-not (= i 10)
     (recur (inc i) (+ x 0.1))))

"1 0.1 0.1"
"2 0.2 0.2"
"3 0.30000000000000004 0.30000000000000004"
"4 0.4 0.4"
"5 0.5 0.5"
"6 0.6 0.6000000000000001"
"7 0.7 0.7000000000000001"
"8 0.7999999999999999 0.8"
"9 0.8999999999999999 0.9"
"10 0.9999999999999999 1.0"

Apparently adding 0.1 6 times is different from 6 * 0.1:
(rationalize 0.6) => 3/5
(rationalize (* 6 0.1)) => 6000000000000001/10000000000000000
(rationalize (+ 0.1 0.1 0.1 0.1 0.1 0.1)) => 3/5

Even worse is the potential for infinite loops:
(defn trap [x]
   (prn x)
   (cond (= x 1) "How nice. 10 * 0.1 = 1"
         (> x 1) "Whoops. Good thing I had an escape hatch."
         :else (trap (+ x 0.1))))

(trap 0.1)
0.1
0.2
0.30000000000000004
0.4
0.5
0.6
0.7
0.7999999999999999
0.8999999999999999
0.9999999999999999
1.0999999999999999
"Whoops. Good thing I had an escape hatch."

If you've read this far you'll want to take a look at the links Mark  
provided earlier.

Aloha,
David Sletten


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To post to this group, send email to clojure@googlegroups.com
To unsubscribe from this group, send email to 
clojure+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/clojure?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to