Add support for u128, U128, s128 and S128 integers to IO::Buffer. (#15399)

This commit is contained in:
Samuel Williams 2025-12-06 15:55:32 +13:00 committed by GitHub
parent 8f9838476d
commit a7dc53b91c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
Notes: git 2025-12-06 02:56:02 +00:00
Merged-By: ioquatix <samuel@codeotaku.com>
6 changed files with 492 additions and 3 deletions

View File

@ -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;

View File

@ -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

View File

@ -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)
{

View File

@ -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
View File

@ -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

View File

@ -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