diff options
| author | Samuel Williams <samuel.williams@shopify.com> | 2025-12-06 15:55:32 +1300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-12-06 15:55:32 +1300 |
| commit | a7dc53b91c8475323b34d5a332fdb25d190e277d (patch) | |
| tree | 0a3ae38b9f91bf841b0ee76d877da1711ff4791d /numeric.c | |
| parent | 8f9838476dc8cc857859a0a93da285d792be7d3b (diff) | |
Add support for `u128`, `U128`, `s128` and `S128` integers to `IO::Buffer`. (#15399)
Diffstat (limited to 'numeric.c')
| -rw-r--r-- | numeric.c | 223 |
1 files changed, 223 insertions, 0 deletions
@@ -3420,6 +3420,229 @@ rb_num2ull(VALUE val) #endif /* HAVE_LONG_LONG */ +// Conversion functions for unified 128-bit integer structures, +// These work with or without native 128-bit integer support. + +#ifndef HAVE_UINT128_T +// Helper function to build 128-bit value from bignum digits (fallback path). +static inline void +rb_uint128_from_bignum_digits_fallback(rb_uint128_t *result, BDIGIT *digits, size_t length) +{ + // Build the 128-bit value from bignum digits: + for (long i = length - 1; i >= 0; i--) { + // Shift both low and high parts: + uint64_t carry = result->parts.low >> (64 - (SIZEOF_BDIGIT * CHAR_BIT)); + result->parts.low = (result->parts.low << (SIZEOF_BDIGIT * CHAR_BIT)) | digits[i]; + result->parts.high = (result->parts.high << (SIZEOF_BDIGIT * CHAR_BIT)) | carry; + } +} + +// Helper function to convert absolute value of negative bignum to two's complement. +// Ruby stores negative bignums as absolute values, so we need to convert to two's complement. +static inline void +rb_uint128_twos_complement_negate(rb_uint128_t *value) +{ + if (value->parts.low == 0) { + value->parts.high = ~value->parts.high + 1; + } + else { + value->parts.low = ~value->parts.low + 1; + value->parts.high = ~value->parts.high + (value->parts.low == 0 ? 1 : 0); + } +} +#endif + +rb_uint128_t +rb_numeric_to_uint128(VALUE x) +{ + rb_uint128_t result = {0}; + if (RB_FIXNUM_P(x)) { + long value = RB_FIX2LONG(x); + if (value < 0) { + rb_raise(rb_eRangeError, "negative integer cannot be converted to unsigned 128-bit integer"); + } +#ifdef HAVE_UINT128_T + result.value = (uint128_t)value; +#else + result.parts.low = (uint64_t)value; + result.parts.high = 0; +#endif + return result; + } + else if (RB_BIGNUM_TYPE_P(x)) { + if (BIGNUM_NEGATIVE_P(x)) { + rb_raise(rb_eRangeError, "negative integer cannot be converted to unsigned 128-bit integer"); + } + size_t length = BIGNUM_LEN(x); +#ifdef HAVE_UINT128_T + if (length > roomof(SIZEOF_INT128_T, SIZEOF_BDIGIT)) { + rb_raise(rb_eRangeError, "bignum too big to convert into 'unsigned 128-bit integer'"); + } + BDIGIT *digits = BIGNUM_DIGITS(x); + result.value = 0; + for (long i = length - 1; i >= 0; i--) { + result.value = (result.value << (SIZEOF_BDIGIT * CHAR_BIT)) | digits[i]; + } +#else + // Check if bignum fits in 128 bits (16 bytes) + if (length > roomof(16, SIZEOF_BDIGIT)) { + rb_raise(rb_eRangeError, "bignum too big to convert into 'unsigned 128-bit integer'"); + } + BDIGIT *digits = BIGNUM_DIGITS(x); + rb_uint128_from_bignum_digits_fallback(&result, digits, length); +#endif + return result; + } + else { + rb_raise(rb_eTypeError, "not an integer"); + } +} + +rb_int128_t +rb_numeric_to_int128(VALUE x) +{ + rb_int128_t result = {0}; + if (RB_FIXNUM_P(x)) { + long value = RB_FIX2LONG(x); +#ifdef HAVE_UINT128_T + result.value = (int128_t)value; +#else + if (value < 0) { + // Two's complement representation: for negative values, sign extend + // Convert to unsigned: for -1, we want all bits set + result.parts.low = (uint64_t)value; // This will be the two's complement representation + result.parts.high = UINT64_MAX; // Sign extend: all bits set for negative + } + else { + result.parts.low = (uint64_t)value; + result.parts.high = 0; + } +#endif + return result; + } + else if (RB_BIGNUM_TYPE_P(x)) { + size_t length = BIGNUM_LEN(x); +#ifdef HAVE_UINT128_T + if (length > roomof(SIZEOF_INT128_T, SIZEOF_BDIGIT)) { + rb_raise(rb_eRangeError, "bignum too big to convert into 'signed 128-bit integer'"); + } + BDIGIT *digits = BIGNUM_DIGITS(x); + uint128_t unsigned_result = 0; + for (long i = length - 1; i >= 0; i--) { + unsigned_result = (unsigned_result << (SIZEOF_BDIGIT * CHAR_BIT)) | digits[i]; + } + if (BIGNUM_NEGATIVE_P(x)) { + // Convert from two's complement + // Maximum negative value is 2^127 + if (unsigned_result > ((uint128_t)1 << 127)) { + rb_raise(rb_eRangeError, "bignum too big to convert into 'signed 128-bit integer'"); + } + result.value = -(int128_t)(unsigned_result - 1) - 1; + } + else { + // Maximum positive value is 2^127 - 1 + if (unsigned_result > (((uint128_t)1 << 127) - 1)) { + rb_raise(rb_eRangeError, "bignum too big to convert into 'signed 128-bit integer'"); + } + result.value = (int128_t)unsigned_result; + } +#else + if (length > roomof(16, SIZEOF_BDIGIT)) { + rb_raise(rb_eRangeError, "bignum too big to convert into 'signed 128-bit integer'"); + } + BDIGIT *digits = BIGNUM_DIGITS(x); + rb_uint128_t unsigned_result = {0}; + rb_uint128_from_bignum_digits_fallback(&unsigned_result, digits, length); + if (BIGNUM_NEGATIVE_P(x)) { + // Check if value fits in signed 128-bit (max negative is 2^127) + uint64_t max_neg_high = (uint64_t)1 << 63; + if (unsigned_result.parts.high > max_neg_high || (unsigned_result.parts.high == max_neg_high && unsigned_result.parts.low > 0)) { + rb_raise(rb_eRangeError, "bignum too big to convert into 'signed 128-bit integer'"); + } + // Convert from absolute value to two's complement (Ruby stores negative as absolute value) + rb_uint128_twos_complement_negate(&unsigned_result); + result.parts.low = unsigned_result.parts.low; + result.parts.high = (int64_t)unsigned_result.parts.high; // Sign extend + } + else { + // Check if value fits in signed 128-bit (max positive is 2^127 - 1) + // Max positive: high = 0x7FFFFFFFFFFFFFFF, low = 0xFFFFFFFFFFFFFFFF + uint64_t max_pos_high = ((uint64_t)1 << 63) - 1; + if (unsigned_result.parts.high > max_pos_high) { + rb_raise(rb_eRangeError, "bignum too big to convert into 'signed 128-bit integer'"); + } + result.parts.low = unsigned_result.parts.low; + result.parts.high = unsigned_result.parts.high; + } +#endif + return result; + } + else { + rb_raise(rb_eTypeError, "not an integer"); + } +} + +VALUE +rb_uint128_to_numeric(rb_uint128_t n) +{ +#ifdef HAVE_UINT128_T + if (n.value <= (uint128_t)RUBY_FIXNUM_MAX) { + return LONG2FIX((long)n.value); + } + return rb_uint128t2big(n.value); +#else + // If high part is zero and low part fits in fixnum + if (n.parts.high == 0 && n.parts.low <= (uint64_t)RUBY_FIXNUM_MAX) { + return LONG2FIX((long)n.parts.low); + } + // Convert to bignum by building it from the two 64-bit parts + VALUE bignum = rb_ull2big(n.parts.low); + if (n.parts.high > 0) { + VALUE high_bignum = rb_ull2big(n.parts.high); + // Multiply high part by 2^64 and add to low part + VALUE shifted_value = rb_int_lshift(high_bignum, INT2FIX(64)); + bignum = rb_int_plus(bignum, shifted_value); + } + return bignum; +#endif +} + +VALUE +rb_int128_to_numeric(rb_int128_t n) +{ +#ifdef HAVE_UINT128_T + if (FIXABLE(n.value)) { + return LONG2FIX((long)n.value); + } + return rb_int128t2big(n.value); +#else + int64_t high = (int64_t)n.parts.high; + // If it's a small positive value that fits in fixnum + if (high == 0 && n.parts.low <= (uint64_t)RUBY_FIXNUM_MAX) { + return LONG2FIX((long)n.parts.low); + } + // Check if it's negative (high bit of high part is set) + if (high < 0) { + // Negative value - convert from two's complement to absolute value + rb_uint128_t unsigned_value = {0}; + if (n.parts.low == 0) { + unsigned_value.parts.low = 0; + unsigned_value.parts.high = ~n.parts.high + 1; + } + else { + unsigned_value.parts.low = ~n.parts.low + 1; + unsigned_value.parts.high = ~n.parts.high + (unsigned_value.parts.low == 0 ? 1 : 0); + } + VALUE bignum = rb_uint128_to_numeric(unsigned_value); + return rb_int_uminus(bignum); + } + else { + // Positive value + return rb_uint128_to_numeric(*(rb_uint128_t*)&n); + } +#endif +} + /******************************************************************** * * Document-class: Integer |
