mirror of
https://github.com/ruby/ruby.git
synced 2026-01-27 04:24:23 +00:00
Add support for u128, U128, s128 and S128 integers to IO::Buffer. (#15399)
This commit is contained in:
parent
8f9838476d
commit
a7dc53b91c
Notes:
git
2025-12-06 02:56:02 +00:00
Merged-By: ioquatix <samuel@codeotaku.com>
6
bignum.c
6
bignum.c
@ -4515,7 +4515,7 @@ rb_str2big_gmp(VALUE arg, int base, int badcheck)
|
||||
|
||||
#if HAVE_LONG_LONG
|
||||
|
||||
static VALUE
|
||||
VALUE
|
||||
rb_ull2big(unsigned LONG_LONG n)
|
||||
{
|
||||
long i;
|
||||
@ -4537,7 +4537,7 @@ rb_ull2big(unsigned LONG_LONG n)
|
||||
return big;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
VALUE
|
||||
rb_ll2big(LONG_LONG n)
|
||||
{
|
||||
long neg = 0;
|
||||
@ -4575,7 +4575,7 @@ rb_ll2inum(LONG_LONG n)
|
||||
#endif /* HAVE_LONG_LONG */
|
||||
|
||||
#ifdef HAVE_INT128_T
|
||||
static VALUE
|
||||
VALUE
|
||||
rb_uint128t2big(uint128_t n)
|
||||
{
|
||||
long i;
|
||||
|
||||
@ -169,7 +169,13 @@ VALUE rb_str2big_gmp(VALUE arg, int base, int badcheck);
|
||||
VALUE rb_int_parse_cstr(const char *str, ssize_t len, char **endp, size_t *ndigits, int base, int flags);
|
||||
RUBY_SYMBOL_EXPORT_END
|
||||
|
||||
#if HAVE_LONG_LONG
|
||||
VALUE rb_ull2big(unsigned LONG_LONG n);
|
||||
VALUE rb_ll2big(LONG_LONG n);
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_INT128_T)
|
||||
VALUE rb_uint128t2big(uint128_t n);
|
||||
VALUE rb_int128t2big(int128_t n);
|
||||
#endif
|
||||
|
||||
|
||||
@ -127,6 +127,49 @@ VALUE rb_int_bit_length(VALUE num);
|
||||
VALUE rb_int_uminus(VALUE num);
|
||||
VALUE rb_int_comp(VALUE num);
|
||||
|
||||
// Unified 128-bit integer structures that work with or without native support:
|
||||
union rb_uint128 {
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
struct {
|
||||
uint64_t high;
|
||||
uint64_t low;
|
||||
} parts;
|
||||
#else
|
||||
struct {
|
||||
uint64_t low;
|
||||
uint64_t high;
|
||||
} parts;
|
||||
#endif
|
||||
#ifdef HAVE_UINT128_T
|
||||
uint128_t value;
|
||||
#endif
|
||||
};
|
||||
typedef union rb_uint128 rb_uint128_t;
|
||||
|
||||
union rb_int128 {
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
struct {
|
||||
uint64_t high;
|
||||
uint64_t low;
|
||||
} parts;
|
||||
#else
|
||||
struct {
|
||||
uint64_t low;
|
||||
uint64_t high;
|
||||
} parts;
|
||||
#endif
|
||||
#ifdef HAVE_UINT128_T
|
||||
int128_t value;
|
||||
#endif
|
||||
};
|
||||
typedef union rb_int128 rb_int128_t;
|
||||
|
||||
// Conversion functions for 128-bit integers:
|
||||
rb_uint128_t rb_numeric_to_uint128(VALUE x);
|
||||
rb_int128_t rb_numeric_to_int128(VALUE x);
|
||||
VALUE rb_uint128_to_numeric(rb_uint128_t n);
|
||||
VALUE rb_int128_to_numeric(rb_int128_t n);
|
||||
|
||||
static inline bool
|
||||
INT_POSITIVE_P(VALUE num)
|
||||
{
|
||||
|
||||
70
io_buffer.c
70
io_buffer.c
@ -1864,6 +1864,9 @@ io_buffer_validate_type(size_t size, size_t offset)
|
||||
// :u64, :U64 | unsigned 64-bit integer.
|
||||
// :s64, :S64 | signed 64-bit integer.
|
||||
//
|
||||
// :u128, :U128 | unsigned 128-bit integer.
|
||||
// :s128, :S128 | signed 128-bit integer.
|
||||
//
|
||||
// :f32, :F32 | 32-bit floating point number.
|
||||
// :f64, :F64 | 64-bit floating point number.
|
||||
|
||||
@ -1895,6 +1898,45 @@ ruby_swapf64(double value)
|
||||
return swap.value;
|
||||
}
|
||||
|
||||
// Structures and conversion functions are now in numeric.h/numeric.c
|
||||
// Unified swap function for 128-bit integers (works with both signed and unsigned)
|
||||
// Since both rb_uint128_t and rb_int128_t have the same memory layout,
|
||||
// we can use a union to make the swap function work with both types
|
||||
static inline rb_uint128_t
|
||||
ruby_swap128_uint(rb_uint128_t x)
|
||||
{
|
||||
rb_uint128_t result;
|
||||
#ifdef HAVE_UINT128_T
|
||||
#if __has_builtin(__builtin_bswap128)
|
||||
result.value = __builtin_bswap128(x.value);
|
||||
#else
|
||||
// Manual byte swap for 128-bit integers
|
||||
uint64_t low = (uint64_t)x.value;
|
||||
uint64_t high = (uint64_t)(x.value >> 64);
|
||||
low = ruby_swap64(low);
|
||||
high = ruby_swap64(high);
|
||||
result.value = ((uint128_t)low << 64) | high;
|
||||
#endif
|
||||
#else
|
||||
// Fallback swap function using two 64-bit integers
|
||||
// For big-endian data on little-endian host (or vice versa):
|
||||
// 1. Swap bytes within each 64-bit part
|
||||
// 2. Swap the order of the parts (since big-endian stores high first, little-endian stores low first)
|
||||
result.parts.low = ruby_swap64(x.parts.high);
|
||||
result.parts.high = ruby_swap64(x.parts.low);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline rb_int128_t
|
||||
ruby_swap128_int(rb_int128_t x)
|
||||
{
|
||||
// Cast to unsigned, swap, then cast back
|
||||
rb_uint128_t u = *(rb_uint128_t*)&x;
|
||||
rb_uint128_t swapped = ruby_swap128_uint(u);
|
||||
return *(rb_int128_t*)&swapped;
|
||||
}
|
||||
|
||||
#define IO_BUFFER_DECLARE_TYPE(name, type, endian, wrap, unwrap, swap) \
|
||||
static ID RB_IO_BUFFER_DATA_TYPE_##name; \
|
||||
\
|
||||
@ -1941,6 +1983,11 @@ IO_BUFFER_DECLARE_TYPE(U64, uint64_t, RB_IO_BUFFER_BIG_ENDIAN, RB_ULL2NUM, RB_NU
|
||||
IO_BUFFER_DECLARE_TYPE(s64, int64_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_LL2NUM, RB_NUM2LL, ruby_swap64)
|
||||
IO_BUFFER_DECLARE_TYPE(S64, int64_t, RB_IO_BUFFER_BIG_ENDIAN, RB_LL2NUM, RB_NUM2LL, ruby_swap64)
|
||||
|
||||
IO_BUFFER_DECLARE_TYPE(u128, rb_uint128_t, RB_IO_BUFFER_LITTLE_ENDIAN, rb_uint128_to_numeric, rb_numeric_to_uint128, ruby_swap128_uint)
|
||||
IO_BUFFER_DECLARE_TYPE(U128, rb_uint128_t, RB_IO_BUFFER_BIG_ENDIAN, rb_uint128_to_numeric, rb_numeric_to_uint128, ruby_swap128_uint)
|
||||
IO_BUFFER_DECLARE_TYPE(s128, rb_int128_t, RB_IO_BUFFER_LITTLE_ENDIAN, rb_int128_to_numeric, rb_numeric_to_int128, ruby_swap128_int)
|
||||
IO_BUFFER_DECLARE_TYPE(S128, rb_int128_t, RB_IO_BUFFER_BIG_ENDIAN, rb_int128_to_numeric, rb_numeric_to_int128, ruby_swap128_int)
|
||||
|
||||
IO_BUFFER_DECLARE_TYPE(f32, float, RB_IO_BUFFER_LITTLE_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf32)
|
||||
IO_BUFFER_DECLARE_TYPE(F32, float, RB_IO_BUFFER_BIG_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf32)
|
||||
IO_BUFFER_DECLARE_TYPE(f64, double, RB_IO_BUFFER_LITTLE_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf64)
|
||||
@ -1965,6 +2012,10 @@ io_buffer_buffer_type_size(ID buffer_type)
|
||||
IO_BUFFER_DATA_TYPE_SIZE(U64)
|
||||
IO_BUFFER_DATA_TYPE_SIZE(s64)
|
||||
IO_BUFFER_DATA_TYPE_SIZE(S64)
|
||||
IO_BUFFER_DATA_TYPE_SIZE(u128)
|
||||
IO_BUFFER_DATA_TYPE_SIZE(U128)
|
||||
IO_BUFFER_DATA_TYPE_SIZE(s128)
|
||||
IO_BUFFER_DATA_TYPE_SIZE(S128)
|
||||
IO_BUFFER_DATA_TYPE_SIZE(f32)
|
||||
IO_BUFFER_DATA_TYPE_SIZE(F32)
|
||||
IO_BUFFER_DATA_TYPE_SIZE(f64)
|
||||
@ -2021,6 +2072,11 @@ rb_io_buffer_get_value(const void* base, size_t size, ID buffer_type, size_t *of
|
||||
IO_BUFFER_GET_VALUE(s64)
|
||||
IO_BUFFER_GET_VALUE(S64)
|
||||
|
||||
IO_BUFFER_GET_VALUE(u128)
|
||||
IO_BUFFER_GET_VALUE(U128)
|
||||
IO_BUFFER_GET_VALUE(s128)
|
||||
IO_BUFFER_GET_VALUE(S128)
|
||||
|
||||
IO_BUFFER_GET_VALUE(f32)
|
||||
IO_BUFFER_GET_VALUE(F32)
|
||||
IO_BUFFER_GET_VALUE(f64)
|
||||
@ -2050,6 +2106,10 @@ rb_io_buffer_get_value(const void* base, size_t size, ID buffer_type, size_t *of
|
||||
* * +:U64+: unsigned integer, 8 bytes, big-endian
|
||||
* * +:s64+: signed integer, 8 bytes, little-endian
|
||||
* * +:S64+: signed integer, 8 bytes, big-endian
|
||||
* * +:u128+: unsigned integer, 16 bytes, little-endian
|
||||
* * +:U128+: unsigned integer, 16 bytes, big-endian
|
||||
* * +:s128+: signed integer, 16 bytes, little-endian
|
||||
* * +:S128+: signed integer, 16 bytes, big-endian
|
||||
* * +:f32+: float, 4 bytes, little-endian
|
||||
* * +:F32+: float, 4 bytes, big-endian
|
||||
* * +:f64+: double, 8 bytes, little-endian
|
||||
@ -2287,6 +2347,11 @@ rb_io_buffer_set_value(const void* base, size_t size, ID buffer_type, size_t *of
|
||||
IO_BUFFER_SET_VALUE(s64);
|
||||
IO_BUFFER_SET_VALUE(S64);
|
||||
|
||||
IO_BUFFER_SET_VALUE(u128);
|
||||
IO_BUFFER_SET_VALUE(U128);
|
||||
IO_BUFFER_SET_VALUE(s128);
|
||||
IO_BUFFER_SET_VALUE(S128);
|
||||
|
||||
IO_BUFFER_SET_VALUE(f32);
|
||||
IO_BUFFER_SET_VALUE(F32);
|
||||
IO_BUFFER_SET_VALUE(f64);
|
||||
@ -3859,6 +3924,11 @@ Init_IO_Buffer(void)
|
||||
IO_BUFFER_DEFINE_DATA_TYPE(s64);
|
||||
IO_BUFFER_DEFINE_DATA_TYPE(S64);
|
||||
|
||||
IO_BUFFER_DEFINE_DATA_TYPE(u128);
|
||||
IO_BUFFER_DEFINE_DATA_TYPE(U128);
|
||||
IO_BUFFER_DEFINE_DATA_TYPE(s128);
|
||||
IO_BUFFER_DEFINE_DATA_TYPE(S128);
|
||||
|
||||
IO_BUFFER_DEFINE_DATA_TYPE(f32);
|
||||
IO_BUFFER_DEFINE_DATA_TYPE(F32);
|
||||
IO_BUFFER_DEFINE_DATA_TYPE(f64);
|
||||
|
||||
223
numeric.c
223
numeric.c
@ -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
|
||||
|
||||
@ -405,6 +405,11 @@ class TestIOBuffer < Test::Unit::TestCase
|
||||
:u64 => [0, 2**64-1],
|
||||
:s64 => [-2**63, 0, 2**63-1],
|
||||
|
||||
:U128 => [0, 2**64, 2**127-1, 2**128-1],
|
||||
:S128 => [-2**127, -2**63-1, -1, 0, 2**63, 2**127-1],
|
||||
:u128 => [0, 2**64, 2**127-1, 2**128-1],
|
||||
:s128 => [-2**127, -2**63-1, -1, 0, 2**63, 2**127-1],
|
||||
|
||||
:F32 => [-1.0, 0.0, 0.5, 1.0, 128.0],
|
||||
:F64 => [-1.0, 0.0, 0.5, 1.0, 128.0],
|
||||
}
|
||||
@ -759,4 +764,146 @@ class TestIOBuffer < Test::Unit::TestCase
|
||||
|
||||
assert_predicate buf, :valid?
|
||||
end
|
||||
|
||||
def test_128_bit_integers
|
||||
buffer = IO::Buffer.new(32)
|
||||
|
||||
# Test unsigned 128-bit integers
|
||||
test_values_u128 = [
|
||||
0,
|
||||
1,
|
||||
2**64 - 1,
|
||||
2**64,
|
||||
2**127 - 1,
|
||||
2**128 - 1,
|
||||
]
|
||||
|
||||
test_values_u128.each do |value|
|
||||
buffer.set_value(:u128, 0, value)
|
||||
assert_equal value, buffer.get_value(:u128, 0), "u128: #{value}"
|
||||
|
||||
buffer.set_value(:U128, 0, value)
|
||||
assert_equal value, buffer.get_value(:U128, 0), "U128: #{value}"
|
||||
end
|
||||
|
||||
# Test signed 128-bit integers
|
||||
test_values_s128 = [
|
||||
-2**127,
|
||||
-2**63 - 1,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
2**63,
|
||||
2**127 - 1,
|
||||
]
|
||||
|
||||
test_values_s128.each do |value|
|
||||
buffer.set_value(:s128, 0, value)
|
||||
assert_equal value, buffer.get_value(:s128, 0), "s128: #{value}"
|
||||
|
||||
buffer.set_value(:S128, 0, value)
|
||||
assert_equal value, buffer.get_value(:S128, 0), "S128: #{value}"
|
||||
end
|
||||
|
||||
# Test size_of
|
||||
assert_equal 16, IO::Buffer.size_of(:u128)
|
||||
assert_equal 16, IO::Buffer.size_of(:U128)
|
||||
assert_equal 16, IO::Buffer.size_of(:s128)
|
||||
assert_equal 16, IO::Buffer.size_of(:S128)
|
||||
assert_equal 32, IO::Buffer.size_of([:u128, :u128])
|
||||
end
|
||||
|
||||
def test_integer_endianness_swapping
|
||||
# Test that byte order is swapped correctly for all signed and unsigned integers > 1 byte
|
||||
host_is_le = IO::Buffer::HOST_ENDIAN == IO::Buffer::LITTLE_ENDIAN
|
||||
host_is_be = IO::Buffer::HOST_ENDIAN == IO::Buffer::BIG_ENDIAN
|
||||
|
||||
# Test values that will produce different byte patterns when swapped
|
||||
# Format: [little_endian_type, big_endian_type, test_value, expected_swapped_value]
|
||||
# expected_swapped_value is the result when writing as le_type and reading as be_type
|
||||
# (or vice versa) on a little-endian host
|
||||
test_cases = [
|
||||
[:u16, :U16, 0x1234, 0x3412],
|
||||
[:s16, :S16, 0x1234, 0x3412],
|
||||
[:u32, :U32, 0x12345678, 0x78563412],
|
||||
[:s32, :S32, 0x12345678, 0x78563412],
|
||||
[:u64, :U64, 0x0123456789ABCDEF, 0xEFCDAB8967452301],
|
||||
[:s64, :S64, 0x0123456789ABCDEF, -1167088121787636991],
|
||||
[:u128, :U128, 0x0123456789ABCDEF0123456789ABCDEF, 0xEFCDAB8967452301EFCDAB8967452301],
|
||||
[:u128, :U128, 0x0123456789ABCDEFFEDCBA9876543210, 0x1032547698BADCFEEFCDAB8967452301],
|
||||
[:u128, :U128, 0xFEDCBA98765432100123456789ABCDEF, 0xEFCDAB89674523011032547698BADCFE],
|
||||
[:u128, :U128, 0x123456789ABCDEF0FEDCBA9876543210, 0x1032547698BADCFEF0DEBC9A78563412],
|
||||
[:s128, :S128, 0x0123456789ABCDEF0123456789ABCDEF, -21528975894082904073953971026863512831],
|
||||
[:s128, :S128, 0x0123456789ABCDEFFEDCBA9876543210, 0x1032547698BADCFEEFCDAB8967452301],
|
||||
]
|
||||
|
||||
test_cases.each do |le_type, be_type, value, expected_swapped|
|
||||
buffer_size = IO::Buffer.size_of(le_type)
|
||||
buffer = IO::Buffer.new(buffer_size * 2)
|
||||
|
||||
# Test little-endian round-trip
|
||||
buffer.set_value(le_type, 0, value)
|
||||
result_le = buffer.get_value(le_type, 0)
|
||||
assert_equal value, result_le, "#{le_type}: round-trip failed"
|
||||
|
||||
# Test big-endian round-trip
|
||||
buffer.set_value(be_type, buffer_size, value)
|
||||
result_be = buffer.get_value(be_type, buffer_size)
|
||||
assert_equal value, result_be, "#{be_type}: round-trip failed"
|
||||
|
||||
# Verify byte patterns are different when endianness differs from host
|
||||
le_bytes = buffer.get_string(0, buffer_size)
|
||||
be_bytes = buffer.get_string(buffer_size, buffer_size)
|
||||
|
||||
if host_is_le
|
||||
# On little-endian host: le_type should match host, be_type should be swapped
|
||||
# So the byte patterns should be different (unless value is symmetric)
|
||||
# Read back with opposite endianness to verify swapping
|
||||
result_le_read_as_be = buffer.get_value(be_type, 0)
|
||||
result_be_read_as_le = buffer.get_value(le_type, buffer_size)
|
||||
|
||||
# The swapped reads should NOT equal the original value (unless it's symmetric)
|
||||
# For most values, this will be different
|
||||
if value != 0 && value != -1 && value.abs != 1
|
||||
refute_equal value, result_le_read_as_be, "#{le_type} written, read as #{be_type} should be swapped on LE host"
|
||||
refute_equal value, result_be_read_as_le, "#{be_type} written, read as #{le_type} should be swapped on LE host"
|
||||
end
|
||||
|
||||
# Verify that reading back with correct endianness works
|
||||
assert_equal value, buffer.get_value(le_type, 0), "#{le_type} should read correctly on LE host"
|
||||
assert_equal value, buffer.get_value(be_type, buffer_size), "#{be_type} should read correctly on LE host (with swapping)"
|
||||
elsif host_is_be
|
||||
# On big-endian host: be_type should match host, le_type should be swapped
|
||||
result_le_read_as_be = buffer.get_value(be_type, 0)
|
||||
result_be_read_as_le = buffer.get_value(le_type, buffer_size)
|
||||
|
||||
# The swapped reads should NOT equal the original value (unless it's symmetric)
|
||||
if value != 0 && value != -1 && value.abs != 1
|
||||
refute_equal value, result_le_read_as_be, "#{le_type} written, read as #{be_type} should be swapped on BE host"
|
||||
refute_equal value, result_be_read_as_le, "#{be_type} written, read as #{le_type} should be swapped on BE host"
|
||||
end
|
||||
|
||||
# Verify that reading back with correct endianness works
|
||||
assert_equal value, buffer.get_value(be_type, buffer_size), "#{be_type} should read correctly on BE host"
|
||||
assert_equal value, buffer.get_value(le_type, 0), "#{le_type} should read correctly on BE host (with swapping)"
|
||||
end
|
||||
|
||||
# Verify that when we write with one endianness and read with the opposite,
|
||||
# we get the expected swapped value
|
||||
buffer.set_value(le_type, 0, value)
|
||||
swapped_value_le_to_be = buffer.get_value(be_type, 0)
|
||||
assert_equal expected_swapped, swapped_value_le_to_be, "#{le_type} written, read as #{be_type} should produce expected swapped value"
|
||||
|
||||
# Also verify the reverse direction
|
||||
buffer.set_value(be_type, buffer_size, value)
|
||||
swapped_value_be_to_le = buffer.get_value(le_type, buffer_size)
|
||||
assert_equal expected_swapped, swapped_value_be_to_le, "#{be_type} written, read as #{le_type} should produce expected swapped value"
|
||||
|
||||
# Verify that writing the swapped value back and reading with original endianness
|
||||
# gives us the original value (double-swap should restore original)
|
||||
buffer.set_value(be_type, 0, swapped_value_le_to_be)
|
||||
round_trip_value = buffer.get_value(le_type, 0)
|
||||
assert_equal value, round_trip_value, "#{le_type}/#{be_type}: double-swap should restore original value"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user