diff --git a/include/mruby/boxing_word.h b/include/mruby/boxing_word.h index 75b133c33..afead315f 100644 --- a/include/mruby/boxing_word.h +++ b/include/mruby/boxing_word.h @@ -11,7 +11,7 @@ # define MRB_WORDBOX_NO_FLOAT_TRUNCATE #endif -#if !defined(MRB_NO_FLOAT) && defined(MRB_WORDBOX_NO_FLOAT_TRUNCATE) +#ifndef MRB_NO_FLOAT struct RFloat { MRB_OBJECT_HEADER; mrb_float f; @@ -83,27 +83,22 @@ enum mrb_special_consts { /* * mrb_value representation: * - * 64-bit word with inline float: + * 64-bit word with inline float (rotation encoding, lossless): * nil : ...0000 0000 (all bits are 0) * false : ...0000 0100 (mrb_fixnum(v) != 0) * true : ...0000 1100 * undef : ...0001 0100 * symbol: ...0001 1100 (use only upper 32-bit as symbol value with MRB_64BIT) * fixnum: ...IIII III1 - * float : ...FFFF FF10 (51 bit significands; require MRB_64BIT) + * float : ...FFFF FF10 (rotl64(float64-ADDEND, 3); exponent [-255,+256]) * object: ...PPPP P000 + * (floats outside inline range are heap-allocated as RFloat) * - * 32-bit word with inline float: - * nil : ...0000 0000 (all bits are 0) - * false : ...0000 0100 (mrb_fixnum(v) != 0) - * true : ...0000 1100 - * undef : ...0001 0100 - * symbol: ...SSS1 0100 (symbol occupies 20bits) - * fixnum: ...IIII III1 - * float : ...FFFF FF10 (22 bit significands; require MRB_64BIT) - * object: ...PPPP P000 + * 64-bit word with inline float32 (MRB_USE_FLOAT32): + * float : ...FFFF FF10 (float32 shifted left by 2) + * (other values same as above) * - * and word boxing without inline float (MRB_WORDBOX_NO_FLOAT_TRUNCATE): + * word boxing without inline float (MRB_WORDBOX_NO_FLOAT_TRUNCATE): * nil : ...0000 0000 (all bits are 0) * false : ...0000 0100 (mrb_fixnum(v) != 0) * true : ...0000 1100 @@ -120,10 +115,9 @@ union mrb_value_ { void *p; struct RBasic *bp; #ifndef MRB_NO_FLOAT -#ifndef MRB_WORDBOX_NO_FLOAT_TRUNCATE - mrb_float f; -#else struct RFloat *fp; +#if !defined(MRB_WORDBOX_NO_FLOAT_TRUNCATE) && defined(MRB_USE_FLOAT32) + mrb_float f; #endif #endif struct RInteger *ip; @@ -182,10 +176,13 @@ mrb_integer_func(mrb_value o) { #define mrb_false_p(o) ((o).w == MRB_Qfalse) #define mrb_true_p(o) ((o).w == MRB_Qtrue) #ifndef MRB_NO_FLOAT -#ifndef MRB_WORDBOX_NO_FLOAT_TRUNCATE +#ifdef MRB_WORDBOX_NO_FLOAT_TRUNCATE +#define mrb_float_p(o) WORDBOX_OBJ_TYPE_P(o, FLOAT) +#elif defined(MRB_USE_FLOAT32) #define mrb_float_p(o) WORDBOX_SHIFT_VALUE_P(o, FLOAT) #else -#define mrb_float_p(o) WORDBOX_OBJ_TYPE_P(o, FLOAT) +/* rotation encoding: most floats inline, edge cases on heap */ +#define mrb_float_p(o) (WORDBOX_SHIFT_VALUE_P(o, FLOAT) || WORDBOX_OBJ_TYPE_P(o, FLOAT)) #endif #else #define mrb_float_p(o) FALSE diff --git a/src/class.c b/src/class.c index fae03fd12..828bb5dba 100644 --- a/src/class.c +++ b/src/class.c @@ -3959,7 +3959,7 @@ init_copy(mrb_state *mrb, mrb_value dest, mrb_value obj) case MRB_TT_ISTRUCT: mrb_istruct_copy(dest, obj); break; -#if !defined(MRB_NO_FLOAT) && defined(MRB_WORDBOX_NO_FLOAT_TRUNCATE) +#if !defined(MRB_NO_FLOAT) && defined(MRB_WORD_BOXING) case MRB_TT_FLOAT: { struct RFloat *f = (struct RFloat*)mrb_obj_ptr(dest); diff --git a/src/etc.c b/src/etc.c index 039f5fb7f..400a27e8d 100644 --- a/src/etc.c +++ b/src/etc.c @@ -182,8 +182,51 @@ mrb_obj_id(mrb_value obj) * RFloat object on the heap. * - If `MRB_64BIT` and `MRB_USE_FLOAT32` are defined, it stores the float * in the lower bits of the word, shifted and tagged. - * - Otherwise, it stores the float directly in the word, tagged. + * - Otherwise (64-bit float64), it uses rotation encoding to store the + * float losslessly inline. Floats outside the inline exponent range + * [-255, +256] are heap-allocated as RFloat. */ + +#if !defined(MRB_WORDBOX_NO_FLOAT_TRUNCATE) && !defined(MRB_USE_FLOAT32) +/* + * Rotation-based float encoding for 64-bit + float64. + * + * Encode: rotl64(float64_bits - ADDEND, 3) produces a tagged value + * with bottom 2 bits == 10 (WORDBOX_FLOAT_FLAG). + * Decode: rotl64(tagged_value, 61) + ADDEND recovers the original bits. + * + * The addend shifts the exponent so that biased exponents [768, 1279] + * (actual [-255, +256]) produce the correct tag pattern after rotation. + * This covers all practical float values with full precision. + */ +#define WORDBOX_FLOAT_ROTATE 3 +#define WORDBOX_FLOAT_EXP_MIN (1023 - 255) /* 768 */ +#define WORDBOX_FLOAT_EXP_MAX (1023 + 256) /* 1279 */ +#define WORDBOX_FLOAT_ADDEND ((uint64_t)((int64_t)(WORDBOX_FLOAT_EXP_MIN - (WORDBOX_FLOAT_FLAG << 9)) << 52)) + +static uint64_t +wordbox_rotl64(uint64_t a, int n) +{ + return (a << n) | (a >> (64 - n)); +} + +static uint64_t +wordbox_float64_to_u64(double d) +{ + union { double d; uint64_t u; } u; + u.d = d; + return u.u; +} + +static double +wordbox_u64_to_float64(uint64_t v) +{ + union { double d; uint64_t u; } u; + u.u = v; + return u.d; +} +#endif + MRB_API mrb_value mrb_word_boxing_float_value(mrb_state *mrb, mrb_float f) { @@ -197,7 +240,21 @@ mrb_word_boxing_float_value(mrb_state *mrb, mrb_float f) v.w = 0; v.f = f; v.w = (v.w<<2) | 2; +#elif defined(MRB_64BIT) + { + uint64_t bits = wordbox_float64_to_u64((double)f); + uint64_t exp = (bits >> 52) & 0x7FF; + if (exp >= WORDBOX_FLOAT_EXP_MIN && exp <= WORDBOX_FLOAT_EXP_MAX) { + v.w = (uintptr_t)wordbox_rotl64(bits - WORDBOX_FLOAT_ADDEND, WORDBOX_FLOAT_ROTATE); + } + else { + v.p = mrb_obj_alloc(mrb, MRB_TT_FLOAT, mrb->float_class); + v.fp->f = f; + v.bp->frozen = 1; + } + } #else + /* 32-bit + float32: truncate bottom 2 bits */ v.f = f; v.w = (v.w & ~3) | 2; #endif @@ -207,26 +264,36 @@ mrb_word_boxing_float_value(mrb_state *mrb, mrb_float f) #ifndef MRB_WORDBOX_NO_FLOAT_TRUNCATE /* - * Unboxes an `mrb_value` to an `mrb_float` when word boxing is used and - * float truncation (`MRB_WORDBOX_NO_FLOAT_TRUNCATE`) is not disabled. - * The function extracts the float value from the `mrb_value`'s union - * representation (`u.value`). - * - If `MRB_64BIT` and `MRB_USE_FLOAT32` are defined, the word (`u.w`) - * is right-shifted by 2 bits to retrieve the float. - * - Otherwise, the lower 2 bits of the word (`u.w`) are cleared to - * retrieve the float. + * Unboxes an `mrb_value` to an `mrb_float`. + * - If `MRB_USE_FLOAT32`: right-shift by 2 to retrieve the float. + * - Otherwise (rotation encoding): decode inline floats via rotation, + * or read from heap RFloat for edge cases. */ MRB_API mrb_float mrb_word_boxing_value_float(mrb_value v) { +#if defined(MRB_64BIT) && defined(MRB_USE_FLOAT32) union mrb_value_ u; u.value = v; -#if defined(MRB_64BIT) && defined(MRB_USE_FLOAT32) u.w >>= 2; -#else - u.w &= ~3; -#endif return u.f; +#elif defined(MRB_64BIT) + if ((v.w & WORDBOX_FLOAT_MASK) == WORDBOX_FLOAT_FLAG) { + return (mrb_float)wordbox_u64_to_float64( + wordbox_rotl64((uint64_t)v.w, 64 - WORDBOX_FLOAT_ROTATE) + WORDBOX_FLOAT_ADDEND); + } + else { + union mrb_value_ u; + u.value = v; + return u.fp->f; + } +#else + /* 32-bit + float32: clear tag bits */ + union mrb_value_ u; + u.value = v; + u.w &= ~3; + return u.f; +#endif } #endif #endif /* MRB_NO_FLOAT */