[CLJ-738] <= is incorrect when args include Double/NaN Created: 14/Feb/11 Updated: 01/Mar/13 Resolved: 26/Aug/11 |
|
| Status: | Closed |
| Project: | Clojure |
| Component/s: | None |
| Affects Version/s: | Release 1.3 |
| Fix Version/s: | Release 1.3 |
| Type: | Defect | Priority: | Minor |
| Reporter: | Jason Wolfe | Assignee: | Luke VanderHart |
| Resolution: | Completed | Votes: | 1 |
| Labels: | None | ||
| Environment: |
Mac OS X, Java 6 |
||
| Attachments: |
|
| Patch: | Code and Test |
| Description |
|
user> (<= 10 Double/NaN 1) (on both 1.2 and 1.3 alpha 5). |
| Comments |
| Comment by Jason Wolfe [ 14/Feb/11 7:18 PM ] |
|
The source of the issue seems to be incorrect treatment of boxed NaN: user> (<= 1000 (Double. Double/NaN)) |
| Comment by Alexander Taggart [ 28/Feb/11 11:14 PM ] |
|
Primitive comparisons use java's primitive operators directly, which always return false for NaN, even when testing equality between two NaNs. In clojure, Number comparisons are all logical variations around calls to Numbers.Ops.lt(Number, Number). So a call to (<= x y) is actually a call to (not (< y x)), which eventually uses the primitive < operator. Alas that logical premise doesn't hold when dealing with NaN: user=> (<= 1 Double/NaN) false user=> (not (< Double/NaN 1)) true So the bug is not that boxed NaN is treated incorrectly, but rather: user> (<= 1000 (Double. Double/NaN)) ; becomes !(NaN < 1000) true user> (<= 1000 (double Double/NaN)) ; becomes (1000 <= NaN) false In the original example, since there are more than two args, the primitive looking args were boxed: user=> (<= 10 Double/NaN 1) ; equivalent to logical-and of the following true user=> (<= 10 (Double. Double/NaN)) ; becomes !(NaN < 10) true user=> (<= (Double. Double/NaN) 1) ; becomes !(1 < NaN) true Note however that java object comparisons for NaNs behave differently: NaN is the largest Double, and NaNs equal each other (see the javadoc). If we make object NaN comparisons always return false, we would need to add the rest of the comparison methods to Numbers.Ops. Yet doing so could also make collection sorting algorithms behave oddly, deviating from sorting written in java. Besides, (= NaN NaN) => false is annoying. Clojure already throws out the notion of error-free dividing by zero (which for doubles would otherwise result in NaN or Infinity, depending on the dividend). Perhaps we could similarly error on NaNs passed to clojure numeric ops. They seem to be more trouble than they're worth. That said, people smarter than me thought they were useful. Then there's that -0.0 nonsense... |
| Comment by Jouni K. Seppänen [ 19/Mar/11 3:02 PM ] |
|
On current master, (<= x y) seems to be special-cased by the compiler, but when <= is called dynamically, the bug is still there: user=> (<= 1 Float/NaN) false user=> (let [op <=] (op 1 Float/NaN)) true Since |
| Comment by Alexander Taggart [ 19/Mar/11 6:45 PM ] |
|
Using let forces calling <= as a function rather than inlining Numbers/lte, which means the args are treated as objects not primitives, thus the different behaviour as I discussed earlier. |
| Comment by Aaron Bedra [ 28/Jun/11 6:28 PM ] |
|
Rich, what should the behavior be? |
| Comment by Jouni K. Seppänen [ 29/Jun/11 1:22 AM ] |
|
My suggestion for the behavior is to follow Java (Java Language Specification §15.20.1) and IEEE 754. The java.sun.com site seems to be down right now, but here's a Google cache link: |
| Comment by Rich Hickey [ 29/Jul/11 7:48 AM ] |
|
It should work the same as primitive double in all cases |
| Comment by Luke VanderHart [ 26/Aug/11 11:33 AM ] |
|
Added patches. The problem was that our logic for lte/gte depended on the fact that lte is equivalent to !gt. However, in Java, this assumption is invalid - any comparison involving NaN always yields false. The fix was to adding lte and gte methods to Numbers.Ops directly, rather than implementing everything in terms of lt. This was the only fix I could see that didn't incur the cost of runtime checks for NaN. |