Reputation: 201
During my clojure studies, I found this:
;Clojure 1.4.0
(def neg_inf -1E-400)
(cond
(= neg_inf 0) "Zero"
(< neg_inf 0) "Negative"
(> neg_inf 0) "Positive"
:default "Neither"
)
;#'user/neg_inf
;user=> "Neither"
Is it a bug?
Then, I tried:
(Math/signum -1E-400) ;-0.0
Here's a way I found to discover when some number is considered as -0.0 :
(Math/copySign 1. -0.) ;-1.0
(Math/copySign 1. -1E-99999999999999999999999);-1.0
This way, I can know if a number is negative even if it is ridiculously close to zero.
All of this because I were trying to solve this problem:
(defn hexadecimal-to-degrees [rah ram ras]
{ :pre [ (>= rah 0) (< rah 24) (>= ram 0) (< ram 60) (>= ras 0) (< ras 60) ]
:post [ (>= % 0) ]
}
(/ (+ rah (/ ram 60) (/ ras 3600)) 15)
)
(hexadecimal-to-degrees -1E-400 -1E-400 -1E-400)
;-0.0 ; OMG, no Assert failed here!
Since there is no negative value for Right Ascension (the celestial equivalent of terrestrial longitude), I was testing if the post condition works in a way that guarantees the function will not return a negative value for the RA no matter what numbers I pass to the function..
Upvotes: 8
Views: 1211
Reputation: 45071
I suspect that the (= 0 -1E-400)
uses java's Double.equals()
method under the hood, which treats the positive and negative zeros as unequal. This behavior is a violation of the IEEE floating point standard. The other comparison operators translate to other methods which behave according to the standard, i.e. treat +0.0 and -0.0 as equal.
To get the standard-compliant behavior, use the number-equivalence operator ==
. Thus, (== 0 -0.0)
evaluates to true
.
More about signed and unsigned zero on Wikipedia: http://en.wikipedia.org/wiki/Signed_zero
On a more general note: comparing floating point numbers for equality should always raise suspicions.
Upvotes: 8
Reputation: 3259
Keep in mind that the behavior of Doubles implies a performance compromise between processor time and accuracy. Clojure makes it easy to work with BigDecimals, which are computationally more expensive but have better accuracy guarantees. You can specify BigDecimal numeric literals by appending 'M' after the literal number, and all numeric operators accept BigDecimals. Quick test drive relevant to your example:
user> (type -1E-400)
java.lang.Double
user> (type -1E-400M)
java.lang.BigDecimal
user> (= 0M -1E-400M)
false
user> (<= 0M -1E-400M)
false
user> (< 0M -1E-400M)
false
user> (>= 0M -1E-400M)
true
Upvotes: 2