Step 8: numeric tower — exact/inexact predicates + truncate/remainder/modulo

7 new R7RS primitives on the float-based tower (Number of float unchanged):
- exact? / inexact? — integer detection via Float.is_integer
- exact->inexact / inexact->exact — identity / round-to-integer
- truncate — toward zero (floor for positive, ceil for negative)
- remainder — sign follows dividend (= Float.rem)
- modulo — sign follows divisor

8 new tests (2658/2658 pass). No type system, VM, compiler, or parser changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 22:25:40 +00:00
parent 2f3e727a6f
commit b0a4be0f22
3 changed files with 169 additions and 33 deletions

View File

@@ -123,6 +123,51 @@
:returns "number"
:doc "Round to ndigits decimal places (default 0).")
(define-primitive
"truncate"
:params (((x :as number)))
:returns "number"
:doc "Truncate toward zero.")
(define-primitive
"remainder"
:params (((a :as number) (b :as number)))
:returns "number"
:doc "Remainder — sign follows dividend.")
(define-primitive
"modulo"
:params (((a :as number) (b :as number)))
:returns "number"
:doc "Modulo — sign follows divisor.")
(define-primitive
"exact?"
:params (((x :as number)))
:returns "boolean"
:doc "True if x is exact (integer-valued).")
(define-primitive
"inexact?"
:params (((x :as number)))
:returns "boolean"
:doc "True if x is inexact (non-integer).")
;; --------------------------------------------------------------------------
;; Core — Comparison
;; --------------------------------------------------------------------------
(define-primitive
"exact->inexact"
:params (((x :as number)))
:returns "number"
:doc "Convert exact to inexact (identity for float tower).")
(define-primitive
"inexact->exact"
:params (((x :as number)))
:returns "number"
:doc "Convert inexact to nearest exact integer.")
(define-primitive
"min"
:params (&rest (args :as number))
@@ -156,9 +201,6 @@
:doc "Decrement by 1."
:body (- n 1))
;; --------------------------------------------------------------------------
;; Core — Comparison
;; --------------------------------------------------------------------------
(define-module :core.comparison)
(define-primitive
@@ -174,6 +216,9 @@
:doc "Inequality."
:body (not (= a b)))
;; --------------------------------------------------------------------------
;; Core — Predicates
;; --------------------------------------------------------------------------
(define-primitive
"eq?"
:params (a b)
@@ -220,9 +265,6 @@
:doc "Greater than or equal."
:body (or (> a b) (= a b)))
;; --------------------------------------------------------------------------
;; Core — Predicates
;; --------------------------------------------------------------------------
(define-module :core.predicates)
(define-primitive
@@ -260,6 +302,9 @@
:doc "True if x is a boolean (true or false)."
:body (= (type-of x) "boolean"))
;; --------------------------------------------------------------------------
;; Core — Logic
;; --------------------------------------------------------------------------
(define-primitive
"number?"
:params (x)
@@ -274,6 +319,9 @@
:doc "True if x is a string."
:body (= (type-of x) "string"))
;; --------------------------------------------------------------------------
;; Core — Strings
;; --------------------------------------------------------------------------
(define-primitive
"list?"
:params (x)
@@ -308,9 +356,6 @@
:returns "boolean"
:doc "True if coll contains key. Strings: substring check. Dicts: key check. Lists: membership.")
;; --------------------------------------------------------------------------
;; Core — Logic
;; --------------------------------------------------------------------------
(define-module :core.logic)
(define-primitive
@@ -320,9 +365,6 @@
:doc "Logical negation. Note: and/or are special forms, not primitives."
:body (if x false true))
;; --------------------------------------------------------------------------
;; Core — Strings
;; --------------------------------------------------------------------------
(define-module :core.strings)
(define-primitive
@@ -391,6 +433,9 @@
:returns "string"
:doc "Strip leading/trailing whitespace.")
;; --------------------------------------------------------------------------
;; Core — Collections
;; --------------------------------------------------------------------------
(define-primitive
"split"
:params ((s :as string) &rest (sep :as string))
@@ -433,9 +478,6 @@
:returns "boolean"
:doc "True if string s ends with suffix.")
;; --------------------------------------------------------------------------
;; Core — Collections
;; --------------------------------------------------------------------------
(define-module :core.collections)
(define-primitive
@@ -492,6 +534,9 @@
:returns "any"
:doc "Element at index n, or nil if out of bounds.")
;; --------------------------------------------------------------------------
;; Core — Dict operations
;; --------------------------------------------------------------------------
(define-primitive
"cons"
:params (x (coll :as list))
@@ -534,9 +579,6 @@
:returns "list"
:doc "Consecutive pairs: (1 2 3 4) → ((1 2) (2 3) (3 4)).")
;; --------------------------------------------------------------------------
;; Core — Dict operations
;; --------------------------------------------------------------------------
(define-module :core.dict)
(define-primitive
@@ -545,6 +587,9 @@
:returns "list"
:doc "List of dict keys.")
;; --------------------------------------------------------------------------
;; Stdlib — Format
;; --------------------------------------------------------------------------
(define-primitive
"vals"
:params ((d :as dict))
@@ -575,6 +620,9 @@
:returns "dict"
:doc "Return new dict with keys removed.")
;; --------------------------------------------------------------------------
;; Stdlib — Text
;; --------------------------------------------------------------------------
(define-primitive
"dict-set!"
:params ((d :as dict) key val)
@@ -587,9 +635,6 @@
:returns "any"
:doc "Pour coll into target. List target: convert to list. Dict target: convert pairs to dict.")
;; --------------------------------------------------------------------------
;; Stdlib — Format
;; --------------------------------------------------------------------------
(define-module :stdlib.format)
(define-primitive
@@ -598,6 +643,12 @@
:returns "string"
:doc "Parse ISO date string and format with strftime-style format.")
;; --------------------------------------------------------------------------
;; Stdlib — Style
;; --------------------------------------------------------------------------
;; --------------------------------------------------------------------------
;; Stdlib — Debug
;; --------------------------------------------------------------------------
(define-primitive
"format-decimal"
:params ((val :as number) &rest (places :as number))
@@ -610,15 +661,15 @@
:returns "number"
:doc "Parse string to integer with optional default on failure.")
;; --------------------------------------------------------------------------
;; Type introspection — platform primitives
;; --------------------------------------------------------------------------
(define-primitive
"parse-datetime"
:params ((s :as string))
:returns "string"
:doc "Parse datetime string — identity passthrough (returns string or nil).")
;; --------------------------------------------------------------------------
;; Stdlib — Text
;; --------------------------------------------------------------------------
(define-module :stdlib.text)
(define-primitive
@@ -639,12 +690,6 @@
:returns "string"
:doc "Remove HTML tags from string.")
;; --------------------------------------------------------------------------
;; Stdlib — Style
;; --------------------------------------------------------------------------
;; --------------------------------------------------------------------------
;; Stdlib — Debug
;; --------------------------------------------------------------------------
(define-module :stdlib.debug)
(define-primitive
@@ -653,9 +698,6 @@
:returns "boolean"
:doc "Assert condition is truthy; raise error with message if not.")
;; --------------------------------------------------------------------------
;; Type introspection — platform primitives
;; --------------------------------------------------------------------------
(define-module :stdlib.types)
(define-primitive

View File

@@ -369,3 +369,68 @@
(assert= 1 (my-or 1 2 3))
(assert= 3 (my-or false false 3))
(assert= false (my-or false false false)))))
(defsuite
"numeric-tower"
(deftest
"exact? recognizes integers"
(do
(assert (exact? 42))
(assert (exact? 0))
(assert (exact? -7))
(assert (not (exact? 3.14)))
(assert (not (exact? 0.5)))))
(deftest
"inexact? recognizes non-integers"
(do
(assert (inexact? 3.14))
(assert (inexact? 0.5))
(assert (not (inexact? 42)))
(assert (not (inexact? 0)))))
(deftest
"exact->inexact identity for floats"
(do
(assert (number? (exact->inexact 42)))
(assert= 42 (exact->inexact 42))
(assert= 3.14 (exact->inexact 3.14))))
(deftest
"inexact->exact rounds to integer"
(do
(assert (integer? (inexact->exact 3.7)))
(assert= 4 (inexact->exact 3.7))
(assert= 3 (inexact->exact 3))
(assert= -4 (inexact->exact -3.7))))
(deftest
"truncate toward zero"
(do
(assert= 3 (truncate 3.7))
(assert= -3 (truncate -3.7))
(assert= 3 (truncate 3.2))
(assert= -3 (truncate -3.2))
(assert= 5 (truncate 5))))
(deftest
"remainder sign follows dividend"
(do
(assert= 1 (remainder 7 3))
(assert= -1 (remainder -7 3))
(assert= 1 (remainder 7 -3))
(assert= -1 (remainder -7 -3))))
(deftest
"modulo sign follows divisor"
(do
(assert= 1 (modulo 7 3))
(assert= 2 (modulo -7 3))
(assert= -2 (modulo 7 -3))
(assert= -1 (modulo -7 -3))))
(deftest
"integer preservation through arithmetic"
(do
(assert (integer? (+ 3 4)))
(assert (integer? (* 3 4)))
(assert (integer? (- 10 3)))
(assert (not (integer? (/ 7 2))))
(assert (integer? (/ 6 3)))
(assert (integer? (floor 3.7)))
(assert (integer? (ceil 3.2)))
(assert (integer? (round 3.5)))
(assert (integer? (truncate 3.7))))))