From 58201a30fb18ca14e5b7d275b128fc5ec01d67b9 Mon Sep 17 00:00:00 2001 From: Matthew Pope Date: Thu, 8 Oct 2026 15:41:19 -0700 Subject: [PATCH] feat(types): add experimental `is_inline` to `Int`, `UInt`, `Coefficient`, `Decimal` Reports whether the value's magnitude is stored without a heap allocation. Gated on the `experimental` feature. Tested at the 2^126 inline/heap boundary for each type. --- src/types/decimal/coefficient.rs | 28 ++++++++++++++++++++ src/types/decimal/mod.rs | 25 ++++++++++++++++++ src/types/integer/mod.rs | 44 ++++++++++++++++++++++++++++++++ src/types/overflowing_int.rs | 2 +- 4 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/types/decimal/coefficient.rs b/src/types/decimal/coefficient.rs index c4360c7e2..f0d477449 100644 --- a/src/types/decimal/coefficient.rs +++ b/src/types/decimal/coefficient.rs @@ -98,6 +98,16 @@ impl Coefficient { self.repr.is_zero() } + /// Returns `true` if this value is stored without a heap allocation. + /// + /// Magnitudes that fit in 126 bits are stored inline; larger magnitudes are heap-allocated. + /// This reflects the current in-memory representation, which may change. + #[cfg(feature = "experimental")] + #[inline] + pub fn is_inline(&self) -> bool { + self.repr.is_inline() + } + /// Returns the coefficient as an `Int`, or `None` for negative zero. /// /// This is a **lossless** query, not a value query: an `Int` cannot represent negative zero, @@ -332,4 +342,22 @@ mod coefficient_tests { "Coefficient(-340282366920938463463374607431768211456)" ); } + + #[cfg(feature = "experimental")] + #[rstest::rstest] + #[case::zero(Coefficient::ZERO, true)] + #[case::negative_zero(Coefficient::NEGATIVE_ZERO, true)] + #[case::max_inline(Coefficient::new((1u128 << 126) - 1), true)] + #[case::negative_max_inline( + Coefficient::from_sign_and_value(Sign::Negative, (1u128 << 126) - 1), + true + )] + #[case::smallest_heap(Coefficient::new(1u128 << 126), false)] + #[case::negative_smallest_heap( + Coefficient::from_sign_and_value(Sign::Negative, 1u128 << 126), + false + )] + fn coefficient_is_inline(#[case] value: Coefficient, #[case] expected: bool) { + assert_eq!(value.is_inline(), expected); + } } diff --git a/src/types/decimal/mod.rs b/src/types/decimal/mod.rs index 8076e315e..3113aba15 100644 --- a/src/types/decimal/mod.rs +++ b/src/types/decimal/mod.rs @@ -111,6 +111,16 @@ impl Decimal { self.coefficient.is_zero() } + /// Returns `true` if this value is stored without a heap allocation. + /// + /// Coefficient magnitudes that fit in 126 bits are stored inline; larger magnitudes are + /// heap-allocated. This reflects the current in-memory representation, which may change. + #[cfg(feature = "experimental")] + #[inline] + pub fn is_inline(&self) -> bool { + self.coefficient.is_inline() + } + /// Returns true if this Decimal's coefficient has a negative sign AND a magnitude greater than /// zero. Otherwise, returns false. (Negative zero returns false.) pub fn is_less_than_zero(&self) -> bool { @@ -1041,4 +1051,19 @@ mod decimal_tests { assert_eq!(c.cmp(&d), Ordering::Greater); assert_eq!(d.cmp(&c), Ordering::Less); } + + #[cfg(feature = "experimental")] + #[rstest] + #[case::zero(Decimal::ZERO, true)] + #[case::negative_zero(Decimal::NEGATIVE_ZERO, true)] + #[case::small(Decimal::new(-123, -2), true)] + #[case::max_inline(Decimal::new((1u128 << 126) - 1, 5), true)] + #[case::smallest_heap(Decimal::new(1u128 << 126, 5), false)] + #[case::negative_smallest_heap( + Decimal::new(Coefficient::from_sign_and_value(Sign::Negative, 1u128 << 126), -5), + false + )] + fn decimal_is_inline(#[case] value: Decimal, #[case] expected: bool) { + assert_eq!(value.is_inline(), expected); + } } diff --git a/src/types/integer/mod.rs b/src/types/integer/mod.rs index 080bfe25c..b902b016c 100644 --- a/src/types/integer/mod.rs +++ b/src/types/integer/mod.rs @@ -106,6 +106,16 @@ impl UInt { pub fn is_zero(&self) -> bool { self.data.is_zero() } + + /// Returns `true` if this value is stored without a heap allocation. + /// + /// Magnitudes that fit in 126 bits are stored inline; larger magnitudes are heap-allocated. + /// This reflects the current in-memory representation, which may change. + #[cfg(feature = "experimental")] + #[inline] + pub fn is_inline(&self) -> bool { + self.data.is_inline() + } } // This macro makes it possible to turn unsigned int primitives into a UInteger using `.into()`. @@ -383,6 +393,16 @@ impl Int { self.data.is_zero() } + /// Returns `true` if this value is stored without a heap allocation. + /// + /// Magnitudes that fit in 126 bits are stored inline; larger magnitudes are heap-allocated. + /// This reflects the current in-memory representation, which may change. + #[cfg(feature = "experimental")] + #[inline] + pub fn is_inline(&self) -> bool { + self.data.is_inline() + } + /// Returns the negation of this value. #[allow(clippy::should_implement_trait)] pub fn neg(self) -> Self { @@ -1065,4 +1085,28 @@ mod integer_tests { assert_eq!(UInt::try_from(int), Ok(uint)); assert!(UInt::try_from(Int::from(-1)).is_err()); } + + #[cfg(feature = "experimental")] + #[rstest] + #[case::zero(Int::ZERO, true)] + #[case::max_inline(Int::from((1i128 << 126) - 1), true)] + #[case::min_inline(Int::from(-((1i128 << 126) - 1)), true)] + #[case::smallest_heap(Int::from(1i128 << 126), false)] + #[case::negative_smallest_heap(Int::from(-(1i128 << 126)), false)] + #[case::i128_min(Int::from(i128::MIN), false)] + #[case::beyond_u128(Int::from(UInt::from_le_bytes(&[0xFF; 17])), false)] + fn int_is_inline(#[case] value: Int, #[case] expected: bool) { + assert_eq!(value.is_inline(), expected); + } + + #[cfg(feature = "experimental")] + #[rstest] + #[case::zero(UInt::ZERO, true)] + #[case::max_inline(UInt::from((1u128 << 126) - 1), true)] + #[case::smallest_heap(UInt::from(1u128 << 126), false)] + #[case::u128_max(UInt::from(u128::MAX), false)] + #[case::beyond_u128(UInt::from_le_bytes(&[0xFF; 17]), false)] + fn uint_is_inline(#[case] value: UInt, #[case] expected: bool) { + assert_eq!(value.is_inline(), expected); + } } diff --git a/src/types/overflowing_int.rs b/src/types/overflowing_int.rs index bcd99f123..62c84036a 100644 --- a/src/types/overflowing_int.rs +++ b/src/types/overflowing_int.rs @@ -247,7 +247,7 @@ impl OverflowingInt { } #[inline] - fn is_inline(&self) -> bool { + pub(crate) fn is_inline(&self) -> bool { (self.raw0() >> TAG_BIT) & 1 == 1 }