;; ==========================================================================
;; test-numeric-tower.sx — Numeric tower: Integer vs Float distinction
;;
;; Tests for float contagion, integer arithmetic, predicates,
;; coercions, parsing, and rendering.
;;
;; Note: Use fractional floats (1.5, 3.14) or exact->inexact for round floats,
;; since the SX serializer renders Number 1.0 as "1" (int form).
;; ==========================================================================

;; --------------------------------------------------------------------------
;; Integer arithmetic — result stays Integer when all args are Integer
;; --------------------------------------------------------------------------

(defsuite
  "numeric-tower:int-arithmetic"
  (deftest "int + int = int" (assert (integer? (+ 1 2))))
  (deftest "int + int value" (assert= (+ 1 2) 3))
  (deftest "int - int = int" (assert (integer? (- 10 3))))
  (deftest "int - int value" (assert= (- 10 3) 7))
  (deftest "int * int = int" (assert (integer? (* 4 5))))
  (deftest "int * int value" (assert= (* 4 5) 20))
  (deftest "zero identity" (assert= (+ 0 0) 0))
  (deftest "negative int" (assert= (- 0 5) -5))
  (deftest
    "int negation is int"
    (assert (integer? (- 0 7))))
  (deftest
    "large int product"
    (assert= (* 100 100) 10000)))

;; --------------------------------------------------------------------------
;; Float contagion — any float arg promotes result to float
;; --------------------------------------------------------------------------

(defsuite
  "numeric-tower:float-contagion"
  (deftest "int + float = float" (assert (float? (+ 1 1.5))))
  (deftest "int + float value" (assert= (+ 1 1.5) 2.5))
  (deftest "float + int = float" (assert (float? (+ 1.5 2))))
  (deftest "float + float = float" (assert (float? (+ 1.5 2.5))))
  (deftest "int * float = float" (assert (float? (* 2 1.5))))
  (deftest "int * float value" (assert= (* 2 1.5) 3))
  (deftest "int - float = float" (assert (float? (- 5 2.5))))
  (deftest "float - int = float" (assert (float? (- 5.5 2))))
  (deftest
    "three args with float"
    (assert (float? (+ 1 2 3.5))))
  (deftest
    "exact->inexact promotes to float"
    (assert (float? (exact->inexact 5)))))

;; --------------------------------------------------------------------------
;; Division
;; --------------------------------------------------------------------------

(defsuite
  "numeric-tower:division"
  (deftest
    "exact division value"
    (assert= (/ 6 2) 3))
  (deftest "inexact division value" (assert= (/ 1 4) 0.25))
  (deftest "float / float = float" (assert (float? (/ 3.5 2.5))))
  (deftest
    "rational / int = rational"
    (assert (rational? (/ 1/2 2))))
  (deftest "rational division value" (assert= (/ 1/2 2) 1/4)))

;; --------------------------------------------------------------------------
;; Type predicates
;; --------------------------------------------------------------------------

(defsuite
  "numeric-tower:predicates"
  (deftest "integer? on int" (assert (integer? 42)))
  (deftest "integer? on negative" (assert (integer? -7)))
  (deftest "integer? on zero" (assert (integer? 0)))
  (deftest
    "integer? on float-int"
    (assert (integer? (exact->inexact 2))))
  (deftest "integer? on fractional float" (assert (not (integer? 1.5))))
  (deftest "float? on 1.5" (assert (float? 1.5)))
  (deftest
    "float? on exact->inexact"
    (assert (float? (exact->inexact 2))))
  (deftest "float? on int" (assert (not (float? 42))))
  (deftest "number? on int" (assert (number? 42)))
  (deftest "number? on float" (assert (number? 3.14)))
  (deftest "number? on rational" (assert (number? 1/3)))
  (deftest "number? on string" (assert (not (number? "42"))))
  (deftest "exact? on int" (assert (exact? 1)))
  (deftest "exact? on rational" (assert (exact? 1/3)))
  (deftest
    "exact? on exact->inexact"
    (assert (not (exact? (exact->inexact 1)))))
  (deftest "inexact? on 1.5" (assert (inexact? 1.5)))
  (deftest "inexact? on int" (assert (not (inexact? 3)))))

;; --------------------------------------------------------------------------
;; Coercions
;; --------------------------------------------------------------------------

(defsuite
  "numeric-tower:coercions"
  (deftest
    "exact->inexact int"
    (assert= (exact->inexact 3) 3))
  (deftest
    "exact->inexact produces float"
    (assert (float? (exact->inexact 5))))
  (deftest
    "exact->inexact float passthrough"
    (assert= (exact->inexact 1.5) 1.5))
  (deftest "exact->inexact rational" (assert= (exact->inexact 1/4) 0.25))
  (deftest "inexact->exact 1.5" (assert= (inexact->exact 1.5) 2))
  (deftest
    "inexact->exact produces int"
    (assert (integer? (inexact->exact (exact->inexact 4)))))
  (deftest "inexact->exact 2.7" (assert= (inexact->exact 2.7) 3))
  (deftest
    "inexact->exact int passthrough"
    (assert= (inexact->exact 5) 5)))

;; --------------------------------------------------------------------------
;; floor / ceiling / truncate / round — return Integer for floats
;; --------------------------------------------------------------------------

(defsuite
  "numeric-tower:rounding"
  (deftest "floor 3.7" (assert= (floor 3.7) 3))
  (deftest "floor produces int" (assert (integer? (floor 3.7))))
  (deftest "floor negative" (assert= (floor -2.3) -3))
  (deftest "truncate 3.9" (assert= (truncate 3.9) 3))
  (deftest "truncate negative" (assert= (truncate -3.9) -3))
  (deftest "truncate produces int" (assert (integer? (truncate 3.9))))
  (deftest "round 2.3 down" (assert= (round 2.3) 2))
  (deftest "round produces int" (assert (integer? (round 2.3))))
  (deftest
    "floor of int passthrough"
    (assert= (floor 5) 5))
  (deftest "floor of int stays int" (assert (integer? (floor 5)))))

;; --------------------------------------------------------------------------
;; parse-number distinguishes int vs float strings
;; --------------------------------------------------------------------------

(defsuite
  "numeric-tower:parse-number"
  (deftest
    "parse-number int string"
    (assert= (parse-number "42") 42))
  (deftest
    "parse-number int is integer?"
    (assert (integer? (parse-number "42"))))
  (deftest "parse-number 3.14" (assert= (parse-number "3.14") 3.14))
  (deftest
    "parse-number float is float?"
    (assert (float? (parse-number "3.14"))))
  (deftest
    "parse-number 1.5 is float?"
    (assert (float? (parse-number "1.5"))))
  (deftest
    "parse-number negative int"
    (assert= (parse-number "-5") -5))
  (deftest
    "parse-number negative int is integer?"
    (assert (integer? (parse-number "-5"))))
  (deftest "parse-int returns integer" (assert (integer? (parse-int "7"))))
  (deftest "parse-int value" (assert= (parse-int "7") 7)))

;; --------------------------------------------------------------------------
;; Equality across numeric types
;; --------------------------------------------------------------------------

(defsuite
  "numeric-tower:equality"
  (deftest "int = same int" (assert= 5 5))
  (deftest
    "int = float eq"
    (assert (= 1 (exact->inexact 1))))
  (deftest
    "float = int eq"
    (assert (= (exact->inexact 1) 1)))
  (deftest "int != different int" (assert (!= 1 2)))
  (deftest "int < float" (assert (< 1 1.5)))
  (deftest "float > int" (assert (> 2.5 2)))
  (deftest "int <= float" (assert (<= 2 2.5)))
  (deftest "int >= int" (assert (>= 3 3))))

;; --------------------------------------------------------------------------
;; mod / remainder / modulo with integers
;; --------------------------------------------------------------------------

(defsuite
  "numeric-tower:modulo"
  (deftest
    "mod int int = int"
    (assert (integer? (mod 10 3))))
  (deftest "mod value" (assert= (mod 10 3) 1))
  (deftest
    "remainder int int = int"
    (assert (integer? (remainder 10 3))))
  (deftest
    "remainder value"
    (assert= (remainder 10 3) 1)))

;; --------------------------------------------------------------------------
;; min / max with mixed types
;; --------------------------------------------------------------------------

(defsuite
  "numeric-tower:min-max"
  (deftest "min two ints" (assert= (min 3 7) 3))
  (deftest
    "min int result type"
    (assert (integer? (min 3 7))))
  (deftest "max two ints" (assert= (max 3 7) 7))
  (deftest "min with float" (assert= (min 3 2.5) 2.5))
  (deftest "max with float" (assert= (max 3 3.5) 3.5)))

;; --------------------------------------------------------------------------
;; str rendering of int vs float
;; --------------------------------------------------------------------------

(defsuite
  "numeric-tower:stringify"
  (deftest "str of int" (assert= (str 42) "42"))
  (deftest "str of negative int" (assert= (str -5) "-5"))
  (deftest "str of 3.14" (assert= (str 3.14) "3.14"))
  (deftest "str of 1.5" (assert= (str 1.5) "1.5")))
