Double -> float coercion giving incorrect numbers

32 views
Skip to first unread message

Lee Hinman

unread,
Apr 28, 2011, 12:02:44 PM4/28/11
to Clojure Dev
I'm seeing this at a REPL (and in tests) with clojure 1.3.0-alpha6:

;; Clojure 1.2.1
user=> (float 3.14)
3.14

;; Clojure 1.3.0-alpha6
user=> (float 3.14)
3.140000104904175

Is this a known issue, or should I open a bug?

ataggart

unread,
Apr 28, 2011, 1:32:42 PM4/28/11
to Clojure Dev
A known issue:
https://github.com/clojure/clojure/blob/master/test/clojure/test_clojure/numbers.clj#L111

For reasons passing understanding, all calls have their results
converted from int->long and float->double.
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Reflector.java#L491

Stuart Halloway

unread,
Apr 29, 2011, 10:56:43 AM4/29/11
to cloju...@googlegroups.com
--
You received this message because you are subscribed to the Google Groups "Clojure Dev" group.
To post to this group, send email to cloju...@googlegroups.com.
To unsubscribe from this group, send email to clojure-dev...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/clojure-dev?hl=en.


Neither an issue nor a bug, but subtle. Taking it step by step:

(1) The reader works with objects, and Clojure's semantics as of 1.3 are built around long and double, so 3.14 is read, as a Double.

(2) The cast to float casts the Double to a float, no surprise there.

(3) The REPL works with objects, so the float is cast back to a Double. You can see identical behavior in Java:

        System.out.println((double) (float) 3.14D);

The casting calls (float, etc.) are for interop only, to select the correct Java method to invoke. So if you have a method with a float overload, you can target it:

(.println System/out (float 3.14))

Cheers,
Stu

Stuart Halloway
Clojure/core
http://clojure.com

Stuart Halloway

unread,
Apr 29, 2011, 11:06:57 AM4/29/11
to cloju...@googlegroups.com
For reasons passing understanding, all calls have their results
converted from int->long and float->double.
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Reflector.java#L491

There are a number of reasons for this. Here are two:

1. The "smaller" types do not add anything useful (in fact many of them don't even have bytecode operations anyway). 

2. The number of types we choose to support causes a combinatorial explosion of signatures in clojure.lang.IFn.

Paul Stadig

unread,
Apr 29, 2011, 1:29:57 PM4/29/11
to cloju...@googlegroups.com
While it is true for byte and short that they do not have the same level of bytecode support as int, it is not true for float and double. They both have the same level of bytecode support, and it seems like a mistake to conflate them.

Maybe this better illustrates the problem?

Clojure 1.2.0
user=> (.println System/out (float 3.14))
3.14
nil
user=> (.println System/out (.floatValue 3.14))
3.14

Clojure 1.3.0-alpha3
user=> (.println System/out (float 3.14))
3.14
nil
user=> (.println System/out (.floatValue 3.14))
3.140000104904175

This last case seems to be the troubling one, because I'm not actually able to invoke the primitive float version of .println, instead the primitive float seems to be cast to a primitive double (or a Double), which causes the slightly different number to be printed.


Paul

Paul Stadig

unread,
Apr 29, 2011, 1:35:23 PM4/29/11
to cloju...@googlegroups.com
Also, it seems like it's impossible to determine if you actually have a float vs. a double.

Clojure 1.3.0-alpha3
user=> (type (float 3.14))
java.lang.Double
user=> (class (float 3.14))
java.lang.Double
user=> (type (.floatValue 3.14))
java.lang.Double
user=> (class (.floatValue 3.14))
java.lang.Double

Perhaps 'float is actually returning a float, but using it with type and class it gets upcast/boxed to a Double. Do type and class need to be overloaded with a version that takes primitives? Or what is going on here?

Paul

Stuart Halloway

unread,
Apr 29, 2011, 1:47:55 PM4/29/11
to cloju...@googlegroups.com
> While it is true for byte and short that they do not have the same level of bytecode support as int, it is not true for float and double. They both have the same level of bytecode support, and it seems like a mistake to conflate them.
>
> Maybe this better illustrates the problem?
>
(snip)

> user=> (.println System/out (.floatValue 3.14))
> 3.140000104904175
>
> This last case seems to be the troubling one, because I'm not actually able to invoke the primitive float version of .println, instead the primitive float seems to be cast to a primitive double (or a Double), which causes the slightly different number to be printed.

This is what float casts are for:

(.println System/out (float 3.14))

Stu

Stuart Halloway

unread,
Apr 29, 2011, 1:51:12 PM4/29/11
to cloju...@googlegroups.com
Also, it seems like it's impossible to determine if you actually have a float vs. a double.

Clojure 1.3.0-alpha3
user=> (type (float 3.14))
java.lang.Double
user=> (class (float 3.14))
java.lang.Double
user=> (type (.floatValue 3.14))
java.lang.Double
user=> (class (.floatValue 3.14))
java.lang.Double

Perhaps 'float is actually returning a float, but using it with type and class it gets upcast/boxed to a Double. Do type and class need to be overloaded with a version that takes primitives? Or what is going on here?

If you == a REPL, then you always have a Double, as shown above.  Is there a motivating use case where you need a float in Clojure (as opposed to in Java interop)?

Paul Stadig

unread,
Apr 29, 2011, 5:31:51 PM4/29/11
to cloju...@googlegroups.com

I guess this whole thing is moot as of about two hours ago (https://github.com/clojure/clojure/commit/6fb09f402c5448070a2efc64ebd64285480b263f)

Now on master:

Clojure 1.3.0-master-SNAPSHOT
user=> (type (float 3.14))
java.lang.Float
user=> (class (float 3.14))
java.lang.Float

user=> (.println System/out (.floatValue 3.14))
3.14
nil

Paul

Reply all
Reply to author
Forward
0 new messages