This is an automated email from the ASF dual-hosted git repository.

Jefffrey pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new d09e4b434a Make more of i256 available in const code (#10363)
d09e4b434a is described below

commit d09e4b434a269178df9487316efb0051cfb9fa10
Author: Adam Gutglick <[email protected]>
AuthorDate: Sun Jul 19 03:17:40 2026 +0100

    Make more of i256 available in const code (#10363)
    
    # Which issue does this PR close?
    
    - Closes #10362.
    
    # Rationale for this change
    
    I think this makes i256 more ergonomic, and closer to the native
    primitive types.
    
    # What changes are included in this PR?
    
    Introduces two new public functions:
    1. `i256::overflowing_add`
    2. `i256::overflowing_sub`
    
    Makes the following functions const:
    1. `i256::to_i128`
    2. `i256::as_i128`
    3. `i256::wrapping_abs`
    4. `i256::checked_abs`
    5. `i256::wrapping_neg`
    6. `i256::checked_neg`
    7. `i256::wrapping_add`
    8. `i256::checked_add`
    9. `i256::wrapping_sub`
    10. `i256::checked_sub`
    11. `i256::wrapping_mul`
    12. `i256::checked_mul`
    13. `i256::checked_pow`
    14. `i256::wrapping_pow`
    
    # Are these changes tested?
    
    Existing testing suite with some small modifications, and two new tests.
    
    # Are there any user-facing changes?
    
    New functions.
    
    ---------
    
    Signed-off-by: Adam Gutglick <[email protected]>
---
 arrow-buffer/src/bigint/mod.rs | 189 ++++++++++++++++++++++++++++++++++-------
 1 file changed, 157 insertions(+), 32 deletions(-)

diff --git a/arrow-buffer/src/bigint/mod.rs b/arrow-buffer/src/bigint/mod.rs
index aaadbff6d9..6a4b41ee29 100644
--- a/arrow-buffer/src/bigint/mod.rs
+++ b/arrow-buffer/src/bigint/mod.rs
@@ -260,18 +260,22 @@ impl i256 {
 
     /// Converts this `i256` into an `i128` returning `None` if this would 
result
     /// in truncation/overflow
-    pub fn to_i128(self) -> Option<i128> {
+    pub const fn to_i128(self) -> Option<i128> {
         let as_i128 = self.low as i128;
 
         let high_negative = self.high < 0;
         let low_negative = as_i128 < 0;
         let high_valid = self.high == -1 || self.high == 0;
 
-        (high_negative == low_negative && high_valid).then_some(self.low as 
i128)
+        if (high_negative == low_negative) && high_valid {
+            Some(self.low as i128)
+        } else {
+            None
+        }
     }
 
     /// Wraps this `i256` into an `i128`
-    pub fn as_i128(self) -> i128 {
+    pub const fn as_i128(self) -> i128 {
         self.low as i128
     }
 
@@ -326,7 +330,7 @@ impl i256 {
 
     /// Computes the absolute value of this i256
     #[inline]
-    pub fn wrapping_abs(self) -> Self {
+    pub const fn wrapping_abs(self) -> Self {
         // -1 if negative, otherwise 0
         let sa = self.high >> 127;
         let sa = Self::from_parts(sa as u128, sa);
@@ -337,25 +341,33 @@ impl i256 {
 
     /// Computes the absolute value of this i256 returning `None` if `Self == 
Self::MIN`
     #[inline]
-    pub fn checked_abs(self) -> Option<Self> {
-        (self != Self::MIN).then(|| self.wrapping_abs())
+    pub const fn checked_abs(self) -> Option<Self> {
+        if !self.is_eq(Self::MIN) {
+            Some(self.wrapping_abs())
+        } else {
+            None
+        }
     }
 
     /// Negates this i256
     #[inline]
-    pub fn wrapping_neg(self) -> Self {
+    pub const fn wrapping_neg(self) -> Self {
         Self::from_parts(!self.low, !self.high).wrapping_add(i256::ONE)
     }
 
     /// Negates this i256 returning `None` if `Self == Self::MIN`
     #[inline]
-    pub fn checked_neg(self) -> Option<Self> {
-        (self != Self::MIN).then(|| self.wrapping_neg())
+    pub const fn checked_neg(self) -> Option<Self> {
+        if !self.is_eq(Self::MIN) {
+            Some(self.wrapping_neg())
+        } else {
+            None
+        }
     }
 
     /// Performs wrapping addition
     #[inline]
-    pub fn wrapping_add(self, other: Self) -> Self {
+    pub const fn wrapping_add(self, other: Self) -> Self {
         let (low, carry) = self.low.overflowing_add(other.low);
         let high = self.high.wrapping_add(other.high).wrapping_add(carry as _);
         Self { low, high }
@@ -363,14 +375,15 @@ impl i256 {
 
     /// Performs checked addition
     #[inline]
-    pub fn checked_add(self, other: Self) -> Option<Self> {
-        let r = self.wrapping_add(other);
-        ((other.is_negative() && r < self) || (!other.is_negative() && r >= 
self)).then_some(r)
+    pub const fn checked_add(self, other: Self) -> Option<Self> {
+        let (r, overflow) = self.overflowing_add(other);
+
+        if overflow { None } else { Some(r) }
     }
 
     /// Performs wrapping subtraction
     #[inline]
-    pub fn wrapping_sub(self, other: Self) -> Self {
+    pub const fn wrapping_sub(self, other: Self) -> Self {
         let (low, carry) = self.low.overflowing_sub(other.low);
         let high = self.high.wrapping_sub(other.high).wrapping_sub(carry as _);
         Self { low, high }
@@ -378,14 +391,15 @@ impl i256 {
 
     /// Performs checked subtraction
     #[inline]
-    pub fn checked_sub(self, other: Self) -> Option<Self> {
-        let r = self.wrapping_sub(other);
-        ((other.is_negative() && r > self) || (!other.is_negative() && r <= 
self)).then_some(r)
+    pub const fn checked_sub(self, other: Self) -> Option<Self> {
+        let (r, overflow) = self.overflowing_sub(other);
+
+        if overflow { None } else { Some(r) }
     }
 
     /// Performs wrapping multiplication
     #[inline]
-    pub fn wrapping_mul(self, other: Self) -> Self {
+    pub const fn wrapping_mul(self, other: Self) -> Self {
         let (low, high) = mulx(self.low, other.low);
 
         // Compute the high multiples, only impacting the high 128-bits
@@ -398,10 +412,15 @@ impl i256 {
         }
     }
 
+    /// Const helper to check equality of two `i256` instances
+    const fn is_eq(&self, other: Self) -> bool {
+        (self.high == other.high) && (self.low == other.low)
+    }
+
     /// Performs checked multiplication
     #[inline]
-    pub fn checked_mul(self, other: Self) -> Option<Self> {
-        if self == i256::ZERO || other == i256::ZERO {
+    pub const fn checked_mul(self, other: Self) -> Option<Self> {
+        if self.is_eq(Self::ZERO) || other.is_eq(Self::ZERO) {
             return Some(i256::ZERO);
         }
 
@@ -423,18 +442,30 @@ impl i256 {
         let (low, high) = mulx(l_abs.low, r_abs.low);
 
         // Compute the high multiples, only impacting the high 128-bits
-        let hl = (l_abs.high as u128).checked_mul(r_abs.low)?;
-        let lh = l_abs.low.checked_mul(r_abs.high as u128)?;
+        let Some(hl) = (l_abs.high as u128).checked_mul(r_abs.low) else {
+            return None;
+        };
+        let Some(lh) = l_abs.low.checked_mul(r_abs.high as u128) else {
+            return None;
+        };
 
-        let high = high.checked_add(hl)?.checked_add(lh)?;
+        let Some(high) = high.checked_add(hl) else {
+            return None;
+        };
+        let Some(high) = high.checked_add(lh) else {
+            return None;
+        };
 
         // Reverse absolute value, if necessary
         let (low, c) = (low ^ out_sa).overflowing_sub(out_sa);
         let high = (high ^ out_sa).wrapping_sub(out_sa).wrapping_sub(c as 
u128) as i128;
 
         // Check for overflow in final conversion
-        (high.is_negative() == (self.is_negative() ^ other.is_negative()))
-            .then_some(Self { low, high })
+        if high.is_negative() == (self.is_negative() ^ other.is_negative()) {
+            Some(Self { low, high })
+        } else {
+            None
+        }
     }
 
     /// Division operation, returns (quotient, remainder).
@@ -521,7 +552,7 @@ impl i256 {
 
     /// Performs checked exponentiation
     #[inline]
-    pub fn checked_pow(self, mut exp: u32) -> Option<Self> {
+    pub const fn checked_pow(self, mut exp: u32) -> Option<Self> {
         if exp == 0 {
             return Some(i256::from_i128(1));
         }
@@ -531,10 +562,16 @@ impl i256 {
 
         while exp > 1 {
             if (exp & 1) == 1 {
-                acc = acc.checked_mul(base)?;
+                let Some(next) = acc.checked_mul(base) else {
+                    return None;
+                };
+                acc = next;
             }
             exp /= 2;
-            base = base.checked_mul(base)?;
+            let Some(next) = base.checked_mul(base) else {
+                return None;
+            };
+            base = next;
         }
         // since exp!=0, finally the exp must be 1.
         // Deal with the final bit of the exponent separately, since
@@ -545,7 +582,7 @@ impl i256 {
 
     /// Performs wrapping exponentiation
     #[inline]
-    pub fn wrapping_pow(self, mut exp: u32) -> Self {
+    pub const fn wrapping_pow(self, mut exp: u32) -> Self {
         if exp == 0 {
             return i256::from_i128(1);
         }
@@ -716,6 +753,54 @@ impl i256 {
         self.checked_ilog2()
             .unwrap_or_else(|| panic!("ilog2 overflow with {self}"))
     }
+
+    /// Calculates `self + rhs`.
+    ///
+    /// Returns the wrapping sum and a boolean indicating whether arithmetic 
overflow occurred.
+    /// The returned sum wraps around the bounds of [`i256`] when overflow 
occurs.
+    #[inline]
+    pub const fn overflowing_add(self, rhs: Self) -> (Self, bool) {
+        // Add the low limbs and capture the carry into the high limb.
+        let (low, carry) = self.low.overflowing_add(rhs.low);
+
+        // Treat the high limbs as raw two's-complement bit patterns.
+        let high = (self.high as u128)
+            .wrapping_add(rhs.high as u128)
+            .wrapping_add(carry as u128) as i128;
+
+        let result = Self { high, low };
+
+        // Signed overflow occurs when:
+        // - both operands have the same sign, and
+        // - the result has the opposite sign.
+        let overflow = (self.high < 0) == (rhs.high < 0) && (high < 0) != 
(self.high < 0);
+
+        (result, overflow)
+    }
+
+    /// Calculates `self - rhs`.
+    ///
+    /// Returns the wrapping difference and a boolean indicating whether 
arithmetic overflow
+    /// occurred. The returned difference wraps around the bounds of [`i256`] 
when overflow occurs.
+    #[inline]
+    pub const fn overflowing_sub(self, rhs: Self) -> (Self, bool) {
+        // Subtract the low limbs and determine whether we borrowed.
+        let (low, borrow) = self.low.overflowing_sub(rhs.low);
+
+        // Subtract the high limbs as raw bit patterns, including the borrow.
+        let high = (self.high as u128)
+            .wrapping_sub(rhs.high as u128)
+            .wrapping_sub(borrow as u128) as i128;
+
+        let result = Self { high, low };
+
+        // Signed overflow occurs when:
+        // - operands have opposite signs, and
+        // - the result's sign differs from the left operand's sign.
+        let overflow = (self.high < 0) != (rhs.high < 0) && (high < 0) != 
(self.high < 0);
+
+        (result, overflow)
+    }
 }
 
 /// Temporary workaround due to lack of stable const array slicing
@@ -738,8 +823,10 @@ const fn split_array<const N: usize, const M: usize>(vals: 
[u8; N]) -> ([u8; M],
 ///
 /// This mirrors the x86 mulx instruction but for 128-bit types
 #[inline]
-fn mulx(a: u128, b: u128) -> (u128, u128) {
-    let split = |a: u128| (a & (u64::MAX as u128), a >> 64);
+const fn mulx(a: u128, b: u128) -> (u128, u128) {
+    const fn split(a: u128) -> (u128, u128) {
+        (a & (u64::MAX as u128), a >> 64)
+    }
 
     const MASK: u128 = u64::MAX as _;
 
@@ -1240,7 +1327,7 @@ impl Not for i256 {
     }
 }
 
-#[cfg(all(test, not(miri)))] // llvm.x86.subborrow.64 not supported by MIRI
+#[cfg(test)]
 mod tests {
     use super::*;
     use num_traits::Signed;
@@ -1315,6 +1402,7 @@ mod tests {
         let actual = il.wrapping_add(ir);
         let (expected, overflow) = i256::from_bigint_with_overflow(bl.clone() 
+ br.clone());
         assert_eq!(actual, expected);
+        assert_eq!(il.overflowing_add(ir), (expected, overflow));
 
         let checked = il.checked_add(ir);
         match overflow {
@@ -1326,6 +1414,7 @@ mod tests {
         let actual = il.wrapping_sub(ir);
         let (expected, overflow) = i256::from_bigint_with_overflow(bl.clone() 
- br.clone());
         assert_eq!(actual.to_string(), expected.to_string());
+        assert_eq!(il.overflowing_sub(ir), (expected, overflow));
 
         let checked = il.checked_sub(ir);
         match overflow {
@@ -1443,6 +1532,41 @@ mod tests {
     }
 
     #[test]
+    fn test_overflowing_add() {
+        const POSITIVE_OVERFLOW: (i256, bool) = 
i256::MAX.overflowing_add(i256::ONE);
+        const NEGATIVE_OVERFLOW: (i256, bool) = 
i256::MIN.overflowing_add(i256::MINUS_ONE);
+
+        assert_eq!(POSITIVE_OVERFLOW, (i256::MIN, true));
+        assert_eq!(NEGATIVE_OVERFLOW, (i256::MAX, true));
+        assert_eq!(
+            i256::from_parts(u128::MAX, 0).overflowing_add(i256::ONE),
+            (i256::from_parts(0, 1), false)
+        );
+        assert_eq!(
+            i256::ONE.overflowing_add(i256::from_i128(2)),
+            (i256::from_i128(3), false)
+        );
+    }
+
+    #[test]
+    fn test_overflowing_sub() {
+        const NEGATIVE_OVERFLOW: (i256, bool) = 
i256::MIN.overflowing_sub(i256::ONE);
+        const POSITIVE_OVERFLOW: (i256, bool) = 
i256::MAX.overflowing_sub(i256::MINUS_ONE);
+
+        assert_eq!(NEGATIVE_OVERFLOW, (i256::MAX, true));
+        assert_eq!(POSITIVE_OVERFLOW, (i256::MIN, true));
+        assert_eq!(
+            i256::from_parts(0, 1).overflowing_sub(i256::ONE),
+            (i256::from_parts(u128::MAX, 0), false)
+        );
+        assert_eq!(
+            i256::from_i128(3).overflowing_sub(i256::from_i128(2)),
+            (i256::ONE, false)
+        );
+    }
+
+    #[test]
+    #[cfg_attr(miri, ignore)]
     fn test_i256() {
         let candidates = [
             i256::ZERO,
@@ -1682,6 +1806,7 @@ mod tests {
     }
 
     #[test]
+    #[cfg_attr(miri, ignore)]
     fn test_decimal256_to_f64_typical_values() {
         let v = i256::from_i128(42_i128);
         assert_eq!(v.to_f64().unwrap(), 42.0);

Reply via email to