@@ -182,32 +182,26 @@ mrb_obj_id(mrb_value obj)
182182 * RFloat object on the heap.
183183 * - If `MRB_64BIT` and `MRB_USE_FLOAT32` are defined, it stores the float
184184 * in the lower bits of the word, shifted and tagged.
185- * - Otherwise (64-bit float64), it uses rotation encoding to store the
186- * float losslessly inline. Floats outside the inline exponent range
187- * [-255, +256] are heap-allocated as RFloat.
185+ * - 64-bit float64: rotation encoding, lossless for exponents [-255, +256].
186+ * - 32-bit float32: rotation encoding, lossless for exponents [-32, +31].
187+ * Floats outside the inline range are heap-allocated as RFloat.
188188 */
189189190-#if !defined(MRB_WORDBOX_NO_FLOAT_TRUNCATE) && !defined(MRB_USE_FLOAT32)
190+#if !defined(MRB_WORDBOX_NO_FLOAT_TRUNCATE) && \
191+ (!defined(MRB_USE_FLOAT32) || !defined(MRB_64BIT))
191192/*
192- * Rotation-based float encoding for 64-bit + float64.
193+ * Rotation-based float encoding (shared between 64-bit float64 and
194+ * 32-bit float32 paths).
193195 *
194- * Encode: rotl64(float64_bits - ADDEND, 3) produces a tagged value
195- * with bottom 2 bits == 10 (WORDBOX_FLOAT_FLAG).
196- * Decode: rotl64(tagged_value, 61) + ADDEND recovers the original bits.
196+ * Encode: rotl(bits - ADDEND, 3) produces a tagged value with bottom
197+ * 2 bits == 10 (WORDBOX_FLOAT_FLAG).
198+ * Decode: rotl(tagged_value, N-3) + ADDEND recovers the original bits.
197199 *
198- * The addend shifts the exponent so that biased exponents [768, 1279]
199- * (actual [-255, +256]) produce the correct tag pattern after rotation.
200- * This covers all practical float values with full precision.
201- *
202- * Special values (0.0, -0.0, +Inf, -Inf) are encoded as small sentinel
203- * constants that also have bottom 2 bits == 10. This avoids heap
204- * allocation for these common values. The 4 obscure floats whose
205- * rotation would collide with a sentinel are heap-allocated instead.
200+ * Special values (0.0, -0.0, +Inf, -Inf, NaN) are encoded as small
201+ * sentinel constants that also have bottom 2 bits == 10. This avoids
202+ * heap allocation for these common values.
206203 */
207204#define WORDBOX_FLOAT_ROTATE 3
208-#define WORDBOX_FLOAT_EXP_MIN (1023 - 255) /* 768 */
209-#define WORDBOX_FLOAT_EXP_MAX (1023 + 256) /* 1279 */
210-#define WORDBOX_FLOAT_ADDEND ((uint64_t)(WORDBOX_FLOAT_EXP_MIN - (WORDBOX_FLOAT_FLAG << 9)) << 52)
211205212206/* sentinel values for special floats (all have & 3 == 2) */
213207#define WORDBOX_FLOAT_PZERO 0x02 /* +0.0 */
@@ -217,6 +211,52 @@ mrb_obj_id(mrb_value obj)
217211#define WORDBOX_FLOAT_NAN 0x12 /* NaN (all NaN bit patterns normalize to this) */
218212#define WORDBOX_FLOAT_SENTINEL_MAX WORDBOX_FLOAT_NAN
219213214+#if defined(MRB_USE_FLOAT32) && !defined(MRB_64BIT)
215+/*
216+ * 32-bit + float32 rotation.
217+ *
218+ * Biased exponents [95, 158] (actual [-32, +31]) produce the correct
219+ * tag pattern after rotation, covering values ~2.3e-10 to ~4.3e9.
220+ * Out-of-range floats are heap-allocated as RFloat.
221+ */
222+#define WORDBOX_FLOAT32_EXP_MIN 95 /* biased, actual -32 */
223+#define WORDBOX_FLOAT32_EXP_MAX 158 /* biased, actual +31 */
224+#define WORDBOX_FLOAT32_ADDEND ((uint32_t)(WORDBOX_FLOAT32_EXP_MIN - (WORDBOX_FLOAT_FLAG << 6)) << 23)
225+226+static uint32_t
227+wordbox_rotl32(uint32_t a, int n)
228+{
229+return (a << n) | (a >> (32 - n));
230+}
231+232+static uint32_t
233+wordbox_float32_to_u32(float f)
234+{
235+union { float f; uint32_t u; } u;
236+u.f = f;
237+return u.u;
238+}
239+240+static float
241+wordbox_u32_to_float32(uint32_t v)
242+{
243+union { float f; uint32_t u; } u;
244+u.u = v;
245+return u.f;
246+}
247+248+#else /* 64-bit + float64 */
249+/*
250+ * 64-bit + float64 rotation.
251+ *
252+ * Biased exponents [768, 1279] (actual [-255, +256]) produce the
253+ * correct tag pattern. Obscure floats whose rotation would collide
254+ * with a sentinel are heap-allocated instead.
255+ */
256+#define WORDBOX_FLOAT_EXP_MIN (1023 - 255) /* 768 */
257+#define WORDBOX_FLOAT_EXP_MAX (1023 + 256) /* 1279 */
258+#define WORDBOX_FLOAT_ADDEND ((uint64_t)(WORDBOX_FLOAT_EXP_MIN - (WORDBOX_FLOAT_FLAG << 9)) << 52)
259+220260static uint64_t
221261wordbox_rotl64(uint64_t a, int n)
222262{
@@ -238,6 +278,7 @@ wordbox_u64_to_float64(uint64_t v)
238278u.u = v;
239279return u.d;
240280}
281+#endif /* MRB_USE_FLOAT32 && !MRB_64BIT */
241282#endif
242283243284MRB_API mrb_value
@@ -287,9 +328,39 @@ mrb_word_boxing_float_value(mrb_state *mrb, mrb_float f)
287328 }
288329 }
289330#else
290-/* 32-bit + float32: truncate bottom 2 bits */
291-v.f = f;
292-v.w = (v.w & ~3) | 2;
331+/* 32-bit + float32: rotation encoding */
332+ {
333+uint32_t bits = wordbox_float32_to_u32(f);
334+uint32_t exp = (bits >> 23) & 0xFF;
335+if (exp == 0) {
336+/* +0.0 or -0.0 (subnormals also fall here, go to heap) */
337+if (bits == 0u)
338+v.w = WORDBOX_FLOAT_PZERO;
339+else if (bits == 0x80000000u)
340+v.w = WORDBOX_FLOAT_NZERO;
341+else goto float_heap;
342+ }
343+else if (exp == 0xFF) {
344+/* +Inf, -Inf, or NaN */
345+if (bits == 0x7F800000u)
346+v.w = WORDBOX_FLOAT_PINF;
347+else if (bits == 0xFF800000u)
348+v.w = WORDBOX_FLOAT_NINF;
349+else
350+v.w = WORDBOX_FLOAT_NAN;
351+ }
352+else if (exp >= WORDBOX_FLOAT32_EXP_MIN && exp <= WORDBOX_FLOAT32_EXP_MAX) {
353+uintptr_t w = (uintptr_t)wordbox_rotl32(bits - WORDBOX_FLOAT32_ADDEND, WORDBOX_FLOAT_ROTATE);
354+if (w <= WORDBOX_FLOAT_SENTINEL_MAX) goto float_heap;
355+v.w = w;
356+ }
357+else {
358+float_heap:
359+v.p = mrb_obj_alloc(mrb, MRB_TT_FLOAT, mrb->float_class);
360+v.fp->f = f;
361+v.bp->frozen = 1;
362+ }
363+ }
293364#endif
294365return v.value;
295366}
@@ -298,9 +369,9 @@ mrb_word_boxing_float_value(mrb_state *mrb, mrb_float f)
298369#ifndef MRB_WORDBOX_NO_FLOAT_TRUNCATE
299370/*
300371 * Unboxes an `mrb_value` to an `mrb_float`.
301- * - If `MRB_USE_FLOAT32`: right-shift by 2 to retrieve the float.
302- * - Otherwise (rotation encoding): decode inline floats via rotation,
303- * or read from heap RFloat for edge cases.
372+ * - 64-bit + float32: right-shift by 2 to retrieve the float.
373+ * - 64-bit + float64 / 32-bit + float32 (rotation encoding):
374+ * decode inline floats via rotation, or read from heap RFloat.
304375 */
305376MRB_API mrb_float
306377mrb_word_boxing_value_float(mrb_value v)
@@ -331,11 +402,26 @@ mrb_word_boxing_value_float(mrb_value v)
331402return u.fp->f;
332403 }
333404#else
334-/* 32-bit + float32: clear tag bits */
335-union mrb_value_ u;
336-u.value = v;
337-u.w &= ~3;
338-return u.f;
405+/* 32-bit + float32: rotation decoding */
406+if ((v.w & WORDBOX_FLOAT_MASK) == WORDBOX_FLOAT_FLAG) {
407+if (v.w <= WORDBOX_FLOAT_SENTINEL_MAX) {
408+switch (v.w) {
409+case WORDBOX_FLOAT_PZERO: return (mrb_float)( 0.0f);
410+case WORDBOX_FLOAT_NZERO: return (mrb_float)(-0.0f);
411+case WORDBOX_FLOAT_PINF: return (mrb_float)( INFINITY);
412+case WORDBOX_FLOAT_NINF: return (mrb_float)(-INFINITY);
413+case WORDBOX_FLOAT_NAN: return (mrb_float) NAN;
414+default: break; /* not reached */
415+ }
416+ }
417+return (mrb_float)wordbox_u32_to_float32(
418+wordbox_rotl32((uint32_t)v.w, 32 - WORDBOX_FLOAT_ROTATE) + WORDBOX_FLOAT32_ADDEND);
419+ }
420+else {
421+union mrb_value_ u;
422+u.value = v;
423+return u.fp->f;
424+ }
339425#endif
340426}
341427#endif