;; ==========================================================================
;; test-rationals.sx — Rational number type: literals, arithmetic, tower
;;
;; Note: in the JS host, (/ int int) returns float (backward-compatible).
;; Use rational literals (1/3, 3/4) or make-rational for exact rationals.
;; ==========================================================================

;; --------------------------------------------------------------------------
;; Literals and type
;; --------------------------------------------------------------------------

(defsuite
  "rationals:literals"
  (deftest "1/3 is rational" (assert (rational? 1/3)))
  (deftest "1/2 is rational" (assert (rational? 1/2)))
  (deftest "2/3 is rational" (assert (rational? 2/3)))
  (deftest "literal numerator 1/3" (assert= (numerator 1/3) 1))
  (deftest "literal denominator 1/3" (assert= (denominator 1/3) 3))
  (deftest "literal numerator 2/3" (assert= (numerator 2/3) 2))
  (deftest "auto-reduce 2/4 = 1/2" (assert= 2/4 1/2))
  (deftest "auto-reduce 6/9 = 2/3" (assert= 6/9 2/3))
  (deftest "negative literal" (assert= (numerator -1/3) -1)))

;; --------------------------------------------------------------------------
;; Constructor and predicates
;; --------------------------------------------------------------------------

(defsuite
  "rationals:constructor"
  (deftest
    "make-rational basic"
    (assert (rational? (make-rational 1 3))))
  (deftest
    "make-rational reduces"
    (assert= (make-rational 2 4) 1/2))
  (deftest
    "make-rational exact int"
    (assert (integer? (make-rational 6 3))))
  (deftest
    "make-rational 6/3 = 2"
    (assert= (make-rational 6 3) 2))
  (deftest
    "make-rational negative"
    (assert= (numerator (make-rational -1 3)) -1))
  (deftest
    "make-rational neg denom"
    (assert= (numerator (make-rational 1 -3)) -1))
  (deftest "rational? on int" (assert (not (rational? 5))))
  (deftest "rational? on float" (assert (not (rational? 1.5))))
  (deftest "rational? on string" (assert (not (rational? "1/2"))))
  (deftest "number? on rational" (assert (number? 1/3)))
  (deftest "exact? on rational" (assert (exact? 1/3)))
  (deftest "inexact? on rational" (assert (not (inexact? 1/3))))
  (deftest "integer? on rational" (assert (not (integer? 1/3))))
  (deftest "dict? on rational" (assert (not (dict? 1/3)))))

;; --------------------------------------------------------------------------
;; Accessors
;; --------------------------------------------------------------------------

(defsuite
  "rationals:accessors"
  (deftest "numerator 1/3" (assert= (numerator 1/3) 1))
  (deftest "denominator 1/3" (assert= (denominator 1/3) 3))
  (deftest "numerator 3/4" (assert= (numerator 3/4) 3))
  (deftest "denominator 3/4" (assert= (denominator 3/4) 4))
  (deftest "numerator of int" (assert= (numerator 5) 5))
  (deftest
    "denominator of int"
    (assert= (denominator 5) 1)))

;; --------------------------------------------------------------------------
;; Arithmetic
;; --------------------------------------------------------------------------

(defsuite
  "rationals:arithmetic"
  (deftest "add two rationals" (assert= (+ 1/3 1/3) 2/3))
  (deftest "add to integer" (assert= (+ 1 1/2) 3/2))
  (deftest "add integer to rational" (assert= (+ 1/2 1) 3/2))
  (deftest "add reduces" (assert= (+ 1/6 1/6) 1/3))
  (deftest "add to whole number" (assert (integer? (+ 1/2 1/2))))
  (deftest "add whole = 1" (assert= (+ 1/2 1/2) 1))
  (deftest "subtract rationals" (assert= (- 3/4 1/4) 1/2))
  (deftest "subtract int from rational" (assert= (- 3/2 1) 1/2))
  (deftest "negate rational" (assert= (- 1/3) -1/3))
  (deftest "multiply rationals" (assert= (* 2/3 3/4) 1/2))
  (deftest "multiply int and rational" (assert= (* 2 1/3) 2/3))
  (deftest "multiply reduces to int" (assert (integer? (* 3 1/3))))
  (deftest "divide rational by int" (assert= (/ 2/3 2) 1/3))
  (deftest "divide rational by rational" (assert= (/ 1/2 1/4) 2))
  (deftest
    "divide rational gives int when exact"
    (assert (integer? (/ 1/2 1/2)))))

;; --------------------------------------------------------------------------
;; Float contagion
;; --------------------------------------------------------------------------

(defsuite
  "rationals:float-contagion"
  (deftest "rational + float = float" (assert (float? (+ 1/3 0.5))))
  (deftest "float + rational = float" (assert (float? (+ 0.5 1/3))))
  (deftest "rational * float = float" (assert (float? (* 1/2 2))))
  (deftest "rational - float = float" (assert (float? (- 1/2 0.1)))))

;; --------------------------------------------------------------------------
;; Comparison
;; --------------------------------------------------------------------------

(defsuite
  "rationals:comparison"
  (deftest "equal rationals" (assert (= 1/2 1/2)))
  (deftest "equal reduced" (assert (= 2/4 1/2)))
  (deftest "not equal" (assert (not (= 1/3 1/2))))
  (deftest "less than" (assert (< 1/3 1/2)))
  (deftest "less than int" (assert (< 1/3 1)))
  (deftest "greater than" (assert (> 2/3 1/2)))
  (deftest "less equal" (assert (<= 1/3 1/3)))
  (deftest "greater equal" (assert (>= 2/3 2/3)))
  (deftest "rational less than float" (assert (< 1/3 0.5))))

;; --------------------------------------------------------------------------
;; Coercion
;; --------------------------------------------------------------------------

(defsuite
  "rationals:coercion"
  (deftest "exact->inexact 1/2" (assert= (exact->inexact 1/2) 0.5))
  (deftest "exact->inexact 1/4" (assert= (exact->inexact 1/4) 0.25))
  (deftest
    "exact->inexact 1/3 is float"
    (assert (float? (exact->inexact 1/3))))
  (deftest "number->string 1/2" (assert= (number->string 1/2) "1/2"))
  (deftest "number->string 3/4" (assert= (number->string 3/4) "3/4")))
