Skip to content

Commit 42e435c

Browse files
leifericfclaude
andcommitted
Numeric tower: (float x) narrows to 32-bit float precision
float.cljc asserts (float r/min-double) equals (float 0.0) because the value underflows the 32-bit float range, and (float r/max-double) / (float ##Inf) / (float ##-Inf) all throw because they exceed it. mino had (float x) as an identity-on-floats / cast-on-numbers -- no precision narrowing, no range check. Update prim_float: range-check against [-FLT_MAX, FLT_MAX] (NaN exempt, passes through; Inf and overflow throw eval/type MTY001), then narrow precision via (double)(float)d which routes through the hardware float-cast. Internal suite 1476 / 7087 / 0. float.cljc 15/0/4 -> 19/0/0. Partial of the Group C.2 plan: narrows the cast contract without introducing a distinct MINO_FLOAT32 value type. (float? x) / (double? x) distinctions remain a JVM-only divergence. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent dccffad commit 42e435c

4 files changed

Lines changed: 32 additions & 11 deletions

File tree

CHANGELOG.md

1.4 KB
Binary file not shown.

src/mino.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
*/
2828
#define MINO_VERSION_MAJOR 0
2929
#define MINO_VERSION_MINOR 100
30-
#define MINO_VERSION_PATCH 30
30+
#define MINO_VERSION_PATCH 31
3131

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

src/prim/numeric.c

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <stdio.h>
1111
#include <errno.h>
1212
#include <limits.h>
13+
#include <float.h>
1314

1415
/* Integer-overflow-safe arithmetic helpers. GCC >= 5 and Clang >= 3.9
1516
* expose __builtin_*_overflow; MSVC and older compilers fall back to
@@ -1711,20 +1712,34 @@ mino_val_t *prim_byte(mino_state_t *S, mino_val_t *args, mino_env_t *env)
17111712
mino_val_t *prim_float(mino_state_t *S, mino_val_t *args, mino_env_t *env)
17121713
{
17131714
mino_val_t *v;
1715+
double d;
17141716
(void)env;
17151717
if (!mino_is_cons(args) || mino_is_cons(args->as.cons.cdr)) {
17161718
return prim_throw_classified(S, "eval/arity", "MAR001", "float requires one argument");
17171719
}
17181720
v = args->as.cons.car;
1719-
if (v != NULL && v->type == MINO_FLOAT) return v;
1720-
if (v != NULL && v->type == MINO_INT) return mino_float(S, (double)v->as.i);
1721-
if (v != NULL && v->type == MINO_BIGINT)
1722-
return mino_float(S, mino_bigint_to_double(v));
1723-
if (v != NULL && v->type == MINO_RATIO)
1724-
return mino_float(S, mino_ratio_to_double(v));
1725-
if (v != NULL && v->type == MINO_BIGDEC)
1726-
return mino_float(S, mino_bigdec_to_double(v));
1727-
return prim_throw_classified(S, "eval/type", "MTY001", "float: expected a number");
1721+
if (v == NULL) {
1722+
return prim_throw_classified(S, "eval/type", "MTY001", "float: expected a number");
1723+
}
1724+
if (v->type == MINO_FLOAT) d = v->as.f;
1725+
else if (v->type == MINO_INT) d = (double)v->as.i;
1726+
else if (v->type == MINO_BIGINT) d = mino_bigint_to_double(v);
1727+
else if (v->type == MINO_RATIO) d = mino_ratio_to_double(v);
1728+
else if (v->type == MINO_BIGDEC) d = mino_bigdec_to_double(v);
1729+
else
1730+
return prim_throw_classified(S, "eval/type", "MTY001", "float: expected a number");
1731+
/* mino has only one float tier (double), but the `float` cast
1732+
* narrows the contract to the 32-bit float range: values outside
1733+
* [-FLT_MAX, FLT_MAX] (including +/- infinity) throw, and
1734+
* underflow rounds toward zero. NaN passes through. */
1735+
if (d == d) {
1736+
if (d > FLT_MAX || d < -FLT_MAX) {
1737+
return prim_throw_classified(S, "eval/type", "MTY001",
1738+
"float: value out of float range");
1739+
}
1740+
d = (double)(float)d;
1741+
}
1742+
return mino_float(S, d);
17281743
}
17291744

17301745
mino_val_t *prim_parse_long(mino_state_t *S, mino_val_t *args, mino_env_t *env)

tests/arithmetic_test.clj

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,13 @@
100100
(is (= 3 (int 3.7)))
101101
(is (= 5 (int 5)))
102102
(is (= 5.0 (float 5)))
103-
(is (= 3.14 (float 3.14))))
103+
;; (float x) narrows to 32-bit float precision then widens back to
104+
;; double; values that fit exactly round-trip cleanly. 3.14 is not
105+
;; representable in float32 and rounds to the nearest float and back.
106+
(is (= (float 3.14) (double (float 3.14))))
107+
(is (NaN? (float ##NaN)))
108+
(is (thrown? (float ##Inf)))
109+
(is (thrown? (float ##-Inf))))
104110

105111
(deftest integer-overflow-strict-and-primed
106112
;; Plain +/-/*/inc/dec throw on long overflow (matching JVM Clojure's

0 commit comments

Comments
 (0)