Skip to content

Commit 5b75654

Browse files
committed
with-precision: accept JVM RoundingMode symbols
The clojuredocs canon for with-precision passes the rounding mode as a bare java.math.RoundingMode enum symbol (UP, HALF_UP, ...). mino's macro previously inlined that symbol into the *math-context* binding, where it then tripped "unbound symbol" at runtime. Fold the eight enum symbols to their keyword equivalents at expand time, and reject other bare symbols at macro time so user typos surface clearly. The native :keyword surface keeps working unchanged. Caught by the external clojure-test-suite driver: with_precision.cljc errored on load because every assertion writes the JVM symbol form.
1 parent 8194175 commit 5b75654

4 files changed

Lines changed: 59 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Changelog
22

3+
## v0.422.3 — `with-precision` Accepts JVM Rounding Symbols
4+
5+
The `with-precision` macro now accepts the JVM `RoundingMode` enum
6+
constants (`UP`, `DOWN`, `CEILING`, `FLOOR`, `HALF_UP`, `HALF_DOWN`,
7+
`HALF_EVEN`, `UNNECESSARY`) as bare symbols, in addition to mino's
8+
native keyword surface (`:up`, `:down`, ...). The macro folds the
9+
symbol form to the corresponding keyword at expand time. Unknown
10+
symbols surface a clear macro-time error rather than the misleading
11+
runtime "unbound symbol" message. Canon `(with-precision N :rounding
12+
HALF_UP ...)` examples from clojuredocs.org now paste through unchanged.
13+
14+
Caught by the external `clojure-test-suite` driver: `with_precision.cljc`
15+
errored on load because every assertion writes the JVM symbol form.
16+
317
## v0.422.2 — `long` Coercion Stays Fixnum
418

519
Fixes `(long x)` silently promoting to `:bigint` when the coerced

src/core.clj

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3049,14 +3049,37 @@
30493049
call."
30503050
nil)
30513051

3052+
(def ^:private rounding-symbol->keyword
3053+
;; JVM RoundingMode enum constants → mino's :keyword surface. Lets
3054+
;; canonical clojuredocs-shaped examples (which write the mode as a
3055+
;; bare symbol, e.g. (with-precision 1 :rounding HALF_UP ...)) paste
3056+
;; through without translation.
3057+
'{UP :up
3058+
DOWN :down
3059+
CEILING :ceiling
3060+
FLOOR :floor
3061+
HALF_UP :half-up
3062+
HALF_DOWN :half-down
3063+
HALF_EVEN :half-even
3064+
UNNECESSARY :unnecessary})
3065+
30523066
(defmacro with-precision
30533067
"Sets *math-context* to {:precision precision :rounding-mode mode}
30543068
around body. The keyword :rounding takes the next form as a
30553069
rounding-mode keyword (e.g. (with-precision 5 :rounding :half-up
3056-
(/ 1M 3M))). Without :rounding, the mode defaults to :half-up."
3070+
(/ 1M 3M))) or as a JVM RoundingMode enum symbol (e.g. HALF_UP,
3071+
CEILING). Without :rounding, the mode defaults to :half-up."
30573072
[precision & body]
30583073
(let [has-rounding? (and (seq body) (= :rounding (first body)))
3059-
mode (if has-rounding? (second body) :half-up)
3074+
raw-mode (if has-rounding? (second body) :half-up)
3075+
mode (if (symbol? raw-mode)
3076+
(or (rounding-symbol->keyword raw-mode)
3077+
(throw (str "with-precision: unknown rounding mode "
3078+
raw-mode
3079+
" (expected UP, DOWN, CEILING, FLOOR, "
3080+
"HALF_UP, HALF_DOWN, HALF_EVEN, "
3081+
"or UNNECESSARY)")))
3082+
raw-mode)
30603083
actual-body (if has-rounding? (drop 2 body) body)]
30613084
`(binding [*math-context* {:precision ~precision
30623085
:rounding-mode ~mode}]

src/mino.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
*/
4848
#define MINO_VERSION_MAJOR 0
4949
#define MINO_VERSION_MINOR 422
50-
#define MINO_VERSION_PATCH 2
50+
#define MINO_VERSION_PATCH 3
5151

5252
/*
5353
* Human-readable version string of the *linked* runtime, e.g. "0.48.0".

tests/numeric_edges_test.clj

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,25 @@
127127
;; classified) so user typos surface immediately.
128128
(is (thrown? (with-precision 5 :rounding :bogus-mode (/ 1M 3M)))))
129129

130+
(deftest with-precision-jvm-symbol-modes
131+
;; ClojureDocs / canonical JVM examples pass the rounding mode as a
132+
;; bare java.math.RoundingMode enum symbol (UP, HALF_UP, ...). mino
133+
;; accepts these in addition to its native keyword surface so canon
134+
;; examples paste through unchanged. Verification goes through /
135+
;; since bigdec division is the path that consumes *math-context*.
136+
(is (= 0.3M (with-precision 1 :rounding HALF_UP (/ 25M 100M))))
137+
(is (= 0.2M (with-precision 1 :rounding HALF_DOWN (/ 25M 100M))))
138+
(is (= 0.2M (with-precision 1 :rounding DOWN (/ 25M 100M))))
139+
(is (= 0.3M (with-precision 1 :rounding UP (/ 25M 100M))))
140+
(is (= 0.2M (with-precision 1 :rounding FLOOR (/ 25M 100M))))
141+
(is (= 0.3M (with-precision 1 :rounding CEILING (/ 25M 100M))))
142+
(is (= 0.12M (with-precision 2 :rounding HALF_EVEN (/ 125M 1000M))))
143+
(is (thrown? (with-precision 5 :rounding UNNECESSARY (/ 1M 3M))))
144+
(is (= 0.5M (with-precision 5 :rounding UNNECESSARY (/ 1M 2M))))
145+
;; A symbol that isn't a RoundingMode enum constant is rejected at
146+
;; macroexpansion, not deferred to a runtime "unbound symbol".
147+
(is (thrown? (eval '(with-precision 5 :rounding NOT_A_MODE (/ 1M 3M))))))
148+
130149
(deftest no-math-context-still-exact-or-throws
131150
;; Outside with-precision, the historical exact-or-throw behavior is
132151
;; preserved. (/ 1M 3M) is non-terminating → throws.

0 commit comments

Comments
 (0)