YYYY




This is in the process of being decommissioned.

For the new stuff, go to
http://blogish.nomistech.com/


Monday, 8 September 2014

An Introduction to Numbers in Clojure

I've been learning about numbers in Clojure. I found that I needed lots of examples to understand things properly. Here they are.

With Clojure 1.6.0.


(ns com.nomistech.clojure-the-language.clojure-numerics-test
  (:require [midje.sweet :refer :all]))

;;;; ___________________________________________________________________________
;;;; ---- Terminology ----

;; The phrase "equivalent value" is used when two numbers, possibly of
;; different types, are the same value as seen from the point of view of
;; mathematics. So, for example, 2 and 2.0 are equivalent values.

;;;; ___________________________________________________________________________
;;;; ---- Clojure's numeric types ----

(fact "Some of Clojure's numeric types are from java.lang"
  (fact (= Byte    java.lang.Byte)    => true)
  (fact (= Short   java.lang.Short)   => true)
  (fact (= Integer java.lang.Integer) => true)
  (fact (= Long    java.lang.Long)    => true)
  (fact (= Float   java.lang.Float)   => true)
  (fact (= Double  java.lang.Double)  => true))

(fact "Some of Clojure's numeric types are from java.math"
  (fact (= BigDecimal java.math.BigDecimal) => true)
  (fact (= BigInteger java.math.BigInteger) => true))

(fact "Some of Clojure's numeric types are from clojure.lang"
  ;; These are not available without the clojure.lang. prefix.
  (fact clojure.lang.BigInt => clojure.lang.BigInt)
  (fact clojure.lang.Ratio  => clojure.lang.Ratio))

(fact "We can produce unboxed values"
  ;; **** Is there a way to make a test of this?
  (byte 2)
  (short 2)
  (int 2)
  (float 2.2)
  (double 2.2))

(fact "Some examples of values that have literal representations"
  (fact (type 2)     => Long)
  (fact (type 2N)    => clojure.lang.BigInt)
  (fact (type 2/3)   => clojure.lang.Ratio)
  (fact (type 2.0M)  => BigDecimal)
  (fact (type 2M)    => BigDecimal)
  (fact (type 2.0)   => Double))

(fact "Some examples of values that do not have literal representations"
  (fact (type (Byte. (byte 2)))     => Byte)
  (fact (type (Short. (short 2)))   => Short)
  (fact (type (Integer. (int 2)))   => Integer)
  (fact (type (BigInteger. "2"))    => BigInteger)
  (fact (type (Float. (float 2.0))) => Float))

(fact "Autoboxing when calling a function"
  (fact (type (byte 2))    => Byte)
  (fact (type (short 2))   => Short)
  (fact (type (int 2))     => Integer)
  (fact (type (long 2))    => Long)
  (fact (type (float 2.0)) => Float))

(fact "Ratios are turned into Longs if possible"
  (type 4/2) => Long)

;; From /Clojure Programming/, p427: "double is the only representation that
;; is inherently inexact".

;;;; ___________________________________________________________________________
;;;; ---- `identical?` ----
;;;; The doc string:
;;;;   Tests whether two arguments are the same object.

;; /Clojure Programming/ p433 says:
;;   In general, numbers will never be identical?, even if provided as
;;   literals.

(fact "In general, two numbers created from two same-looking literals are not identical"
  (fact (identical? 2000 2000) => false)
  (fact (identical? 2N 2N)     => false)
  (fact (identical? 2/3 2/3)   => false)
  (fact (identical? 2M 2M)     => false)
  (fact (identical? 2.0M 2.0M) => false)
  (fact (identical? 2.0 2.0)   => false))

(fact "In some cases, two numbers created from two same-looking literals are identical"
  (identical? 2 2) => true)

(fact "Same-valued fixnums are identical to each other, but same-valued non-fixnums are not identical to each other"
  ;; Explained by /Clojure Programming/ p433 which says:
  ;;   The exception is that the JVM (and therefore Clojure) provides for a
  ;;   limited range of fixnums. Fixnums are a pool of boxed integer values
  ;;   that are always used in preference to allocating a new integer. [...]
  ;;   The Oracle JVM’s fixnum range is ±127.
  ;;     jsk: Actually -128 to +127
  (fact (identical? -129 -129) => false)
  (fact (identical? -128 -128) => true)
  (fact (identical?  127  127) => true)
  (fact (identical?  128  128) => false))

(fact "But, of course, a number object is identical to itself"
  (let [x 2N] (identical? x x)) => true)

(fact "And, of course, numbers with different representations are not identical"
  (fact (identical? 2 2N)   => false)
  (fact (identical? 2 2M)   => false)
  (fact (identical? 2 2.0M) => false)
  (fact (identical? 2 2.0)  => false))

;;;; ___________________________________________________________________________
;;;; ---- `=` ----
;;;; The doc string:
;;;;   Equality. Returns true if x equals y, false if not. Same as Java
;;;;   x.equals(y) except it also works for nil, and compares numbers and
;;;;   collections in a type-independent manner. Clojure's immutable data
;;;;   structures define equals() (and thus =) as a value, not an identity,
;;;;   comparison.

;; I think there's a problem with the doc string:
;; - What does it mean by "in a type-independent manner"?
;;   - Is this defined in any authoritative place?
;;   - The doc string for `==` uses the phrase "type-independent" with a
;;     different (and, to me, intuitive) meaning.
;; - I would expect e.g. (= 2 2M) => true, but that's not so.
;;   - I'm not the only one:
;;     - See http://dev.clojure.org/jira/browse/CLJ-1333.

;; /Clojure Programming/ p433-444 was helpful.

;; We need the notion of categories of numbers. (Is this defined in any
;; authoritative place?)
;; - We have:
;;   - integers (e.g. 2, 2N)
;;   - ratios (e.g. 2/3)
;;   - arbitrary-precision decimals (e.g. 2M, 2.0M)
;;   - limited-precision decimals (e.g. 2.0, (Float. 2.0))

;;;; ---------------------------------------------------------------------------
;;;; ---- Things that are fine ----

(fact "All types of integer are usefully comparable using `=`"
  ;; From /Clojure Programming/ with adjustments
  (= (Byte. (byte 2))
     (Short. (short 2))
     (Integer. (int 2))
     2
     2N)
  => true)

(fact "Ratios are usefully comparable using `=`"
  (= 2/3 2/3)
  => true)

(fact "Arbitrary-precision decimals are usefully comparable using `=`"
  (= 1.25M
     1.25M)
  => true)

(fact "Limited-precision decimals of different widths are usefully comparable using `=`"
  ;; From /Clojure Programming/
  (= 1.25 ; a Double
     (Float. 1.25))
  => true)

;;;; ---------------------------------------------------------------------------
;;;; ---- The thing that is, in my opinion, contrary to the doc string ----

(fact "`=` returns false for comparisons of equivalent numbers of different categories"
  ;; From /Clojure Programming/ with some changes and additions
  (fact (= 2 2.0)    => false)
  (fact (= 2 2M)     => false)
  (fact (= 2N 2M)    => false)
  (fact (= 1.25 5/4) => false))

;; From /Clojure Programming/:
;;   Clojure’s `=` could obviate these type differences (as Ruby and Python
;;   do), but doing so would impose some runtime cost that would be
;;   unappreciated by those who need to maximize the performance of programs
;;   that work with homogeneous numeric data.

;;;; ---------------------------------------------------------------------------
;;;; ---- My playing ----

;; Shows that "same category" is A Thing, as /Clojure Programming/ says. Shows
;; that the doc string's "type-independent manner" is wrong (according to what
;; I think that should mean).

;; Summary, showing the equivalence classes apart from Ratios:
;;;
;;   Key to the table entries:
;;;
;;         =   means   (= x y) is true
;;         .   means   (= x y) is false
;;             where x is the row value and y is the column value
;;;
;;       F2.0  means   (Float. 2.0)
;;;
;; 
;;              --------------------------------------
;;        =     |   2  2N   |  2M 2.0M  |  2.0  F2.0 |
;;     -----------------------------------------------
;;     |        |           |           |            |
;;     |  2     |   =   =   |   .   .   |   .   .    |
;;     |        |           |           |            |
;;     |  2N    |   =   =   |   .   .   |   .   .    |
;;     |        |           |           |            |
;;     -----------------------------------------------
;;     |        |           |           |            |
;;     |  2M    |   .   .   |   =   =   |   .   .    |
;;     |        |           |           |            |
;;     |  2.0M  |   .   .   |   =   =   |   .   .    |
;;     |        |           |           |            |
;;     -----------------------------------------------
;;     |        |           |           |            |
;;     |  2.0   |   .   .   |   .   .   |   =   =    |
;;     |        |           |           |            |
;;     |  F2.0  |   .   .   |   .   .   |   =   =    |
;;     |        |           |           |            |
;;     -----------------------------------------------


(fact "Two numbers of the same type and with equivalent value are equal using `=`"
  (fact (= 2 2)                       => true)
  (fact (= 2N 2N)                     => true)
  (fact (= 2/3 2/3)                   => true)
  (fact (= 2M 2M)                     => true)
  (fact (= 2.0M 2.0M)                 => true)
  (fact (= 2.0 2.0)                   => true)
  (fact (= (Float. 2.0) (Float. 2.0)) => true))

(fact "Two numbers of the same category and with equivalent value are equal using `=`"
  (fact (= 2   2N)           => true)
  (fact (= 2M  2.0M)         => true)
  (fact (= 2.0 (Float. 2.0)) => true))

(fact "Two numbers of different categories are not equal using `=`"
  (fact (= 2    2M)            => false)
  (fact (= 2    2.0M)          => false)
  (fact (= 2    2.0)           => false)
  (fact (= 2    (Float. 2.0))  => false)
  ;;
  (fact (= 2N   2M)            => false)
  (fact (= 2N   2.0M)          => false)
  (fact (= 2N   2.0)           => false)
  (fact (= 2N   (Float. 2.0))  => false)
  ;;
  (fact (= 2M   2.0)           => false)
  (fact (= 2M   (Float. 2.0))  => false)
  ;;
  (fact (= 2.0M 2.0)           => false)
  (fact (= 2.0M (Float. 2.0))  => false))

;;;; ---------------------------------------------------------------------------

(fact "Collections use `=` to determine equality"
  (into #{} [(Byte. (byte 2))
             (Short. (short 2))
             (Integer. (int 2))
             2
             2N
             2M
             2.0M
             2.0
             (Float. 2.0)])
  => #{2 2.0M 2.0})

;;;; ___________________________________________________________________________
;;;; ---- `==` ----
;;;; The doc string:
;;;;   Returns non-nil if nums all have the equivalent value (type-independent),
;;;;   otherwise false.

;; The doc string uses the phrase "type-independent" in a way that is (to me)
;; intuitive, but which has a different meaning to that in the doc string for
;; `=`.

;; From /Clojure Programming/:
;;   Clojure opts to provide a third notion of equality, specifically to
;;   address the need for type-insensitive equivalence tests.

(fact "Numbers with an equivalent value are equal using `==`"
  (== (Byte. (byte 2))
      (Short. (short 2))
      (Integer. (int 2))
      2
      2N
      2M
      2.0M
      2.0
      (Float. 2.0))
  => true)

;;;; ___________________________________________________________________________
;;;; ---- Going from Longs to BigInts and vice versa, or not ----

(def max-long-plus-1 9223372036854775808N)

(fact "About the 'ordinary' arithmetic operators"
  ;;
  (fact "Throw exceptions on overflow"
    (inc Long/MAX_VALUE)
    => (throws ArithmeticException "integer overflow"))
  ;; 
  (fact "We can avoid overflow exceptions by coercing to BigInt first"
    (inc (bigint Long/MAX_VALUE))
    => max-long-plus-1)
  ;; 
  (fact "Do not demote from BigInt"
    (type (- max-long-plus-1 Long/MAX_VALUE))
    => clojure.lang.BigInt))

(fact "About the xxxx' operators"
  ;;
  (fact "Auto-promote"
    (inc' Long/MAX_VALUE)
    => max-long-plus-1)
  ;;
  (fact "Do not promote if unnecessary"
    (type (inc' 1))
    => Long)
  ;;
  (fact "Do not demote"
    (type (dec' (inc' Long/MAX_VALUE)))
    => clojure.lang.BigInt))

(def boxed-max-long Long/MAX_VALUE)

(fact "About the unchecked-xxxx operators"
  ;;
  (fact "Don't check for overflow"
    (unchecked-inc Long/MAX_VALUE)
    => Long/MIN_VALUE)
  ;;
  (fact "Only do what you expect on longs, not Longs"
    ;; The doc strings for unchecked operations only define what happens
    ;; for (unboxed) longs, not for (boxed) Longs.
    ;; - See https://groups.google.com/d/msg/clojure/1tefVmYKmpc/2hKlXU-c13sJ
    (fact "With a boxed value it seems weird"
      (unchecked-inc boxed-max-long)
      => (throws ArithmeticException "integer overflow"))
    (fact "But with an unboxed value it's as you'd expect"
      (let [unboxed-max-long Long/MAX_VALUE]
        (unchecked-inc unboxed-max-long))
      => Long/MIN_VALUE)))

;;;; ___________________________________________________________________________
;;;; ---- Some things about BigInts and Ratios ----

(fact "About the ratio of a BigInt and a Long-or-a-BigInt"
  (let [an-even-big-int (+' Long/MAX_VALUE 1)
        an-odd-big-int  (+' Long/MAX_VALUE 2)]
    (assert (even? an-even-big-int))
    (assert (odd?  an-odd-big-int))
    (assert (= (type an-even-big-int) clojure.lang.BigInt))
    (assert (= (type an-odd-big-int)  clojure.lang.BigInt))
    (fact "If the ratio is an integer it is a BigInt"
      (type (/ an-even-big-int 2))  => clojure.lang.BigInt
      (type (/ an-even-big-int 2N)) => clojure.lang.BigInt)
    (fact "If ratio is not an integer it is a Ratio"
      (type (/ an-odd-big-int 2))   => clojure.lang.Ratio
      (type (/ an-odd-big-int 2N))  => clojure.lang.Ratio)))

No comments:

Post a Comment