From db74e0568dbb40d0f5817f85b95ab97ed14e5d58 Mon Sep 17 00:00:00 2001 From: Raphael Taylor-Davies Date: Mon, 26 Sep 2022 17:12:56 +0100 Subject: [PATCH 1/9] Add i256 (#2637) --- arrow-buffer/src/bigint.rs | 253 +++++++++++++++++++++++++++++++++++++ arrow-buffer/src/lib.rs | 2 + arrow-buffer/src/native.rs | 3 + 3 files changed, 258 insertions(+) create mode 100644 arrow-buffer/src/bigint.rs diff --git a/arrow-buffer/src/bigint.rs b/arrow-buffer/src/bigint.rs new file mode 100644 index 00000000000..e444a56b6d6 --- /dev/null +++ b/arrow-buffer/src/bigint.rs @@ -0,0 +1,253 @@ +use num::BigInt; +use std::cmp::Ordering; + +/// A signed 256-bit integer +#[allow(non_camel_case_types)] +#[derive(Copy, Clone, Default, Eq, PartialEq, Hash)] +pub struct i256([u8; 32]); + +impl std::fmt::Debug for i256 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self) + } +} + +impl std::fmt::Display for i256 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", BigInt::from_signed_bytes_le(&self.0)) + } +} + +impl PartialOrd for i256 { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for i256 { + fn cmp(&self, other: &Self) -> Ordering { + // This is 25x faster than using a variable length encoding such + // as BigInt as it avoids allocation and branching + let (left_low, left_high) = self.as_parts(); + let (right_low, right_high) = other.as_parts(); + left_high.cmp(&right_high).then(left_low.cmp(&right_low)) + } +} + +impl i256 { + /// Create an integer value from its representation as a byte array in little-endian. + #[inline] + pub fn from_le_bytes(b: [u8; 32]) -> Self { + Self(b) + } + + /// Return the memory representation of this integer as a byte array in little-endian byte order. + #[inline] + pub fn to_le_bytes(self) -> [u8; 32] { + self.0 + } + + /// Returns this i256 as a low u128 and high i128 + #[inline] + fn as_parts(self) -> (u128, i128) { + ( + u128::from_le_bytes(self.0[0..16].try_into().unwrap()), + i128::from_le_bytes(self.0[16..32].try_into().unwrap()), + ) + } + + /// Create an i256 from the provided low and high i128 + #[inline] + fn from_parts(low: u128, high: i128) -> Self { + let mut t = [0; 32]; + let t_low: &mut [u8; 16] = (&mut t[0..16]).try_into().unwrap(); + *t_low = low.to_le_bytes(); + let t_high: &mut [u8; 16] = (&mut t[16..32]).try_into().unwrap(); + *t_high = high.to_le_bytes(); + Self(t) + } + + /// Create an i256 from the provided [`BigInt`] returning a bool indicating + /// if overflow occurred + fn from_bigint_with_overflow(v: BigInt) -> (Self, bool) { + let v_bytes = v.to_signed_bytes_le(); + match v_bytes.len().cmp(&32) { + Ordering::Less => { + let mut bytes = if num::Signed::is_negative(&v) { + [255_u8; 32] + } else { + [0; 32] + }; + bytes[0..v_bytes.len()].copy_from_slice(&v_bytes[..v_bytes.len()]); + (Self(bytes), false) + } + Ordering::Equal => (Self(v_bytes.try_into().unwrap()), false), + Ordering::Greater => (Self(v_bytes[..32].try_into().unwrap()), true), + } + } + + /// Performs wrapping addition + #[inline] + pub fn wrapping_add(self, other: Self) -> Self { + let (left_low, left_high) = self.as_parts(); + let (right_low, right_high) = other.as_parts(); + + let (low, carry) = left_low.overflowing_add(right_low); + let high = left_high.wrapping_add(right_high).wrapping_add(carry as _); + Self::from_parts(low, high) + } + + /// Performs checked addition + #[inline] + pub fn checked_add(self, other: Self) -> Option { + let (left_low, left_high) = self.as_parts(); + let (right_low, right_high) = other.as_parts(); + + let (low, carry) = left_low.overflowing_add(right_low); + let high = left_high.checked_add(right_high)?.checked_add(carry as _)?; + Some(Self::from_parts(low, high)) + } + + /// Performs wrapping addition + #[inline] + pub fn wrapping_sub(self, other: Self) -> Self { + let (left_low, left_high) = self.as_parts(); + let (right_low, right_high) = other.as_parts(); + + let (low, carry) = left_low.overflowing_sub(right_low); + let high = left_high.wrapping_sub(right_high).wrapping_sub(carry as _); + Self::from_parts(low, high) + } + + /// Performs checked addition + #[inline] + pub fn checked_sub(self, other: Self) -> Option { + let (left_low, left_high) = self.as_parts(); + let (right_low, right_high) = other.as_parts(); + + let (low, carry) = left_low.overflowing_sub(right_low); + let high = left_high.checked_sub(right_high)?.checked_sub(carry as _)?; + Some(Self::from_parts(low, high)) + } + + /// Performs wrapping multiplication + #[inline] + pub fn wrapping_mul(self, other: Self) -> Self { + let l = BigInt::from_signed_bytes_le(&self.0); + let r = BigInt::from_signed_bytes_le(&other.0); + Self::from_bigint_with_overflow(l * r).0 + } + + /// Performs checked multiplication + #[inline] + pub fn checked_mul(self, other: Self) -> Option { + let l = BigInt::from_signed_bytes_le(&self.0); + let r = BigInt::from_signed_bytes_le(&other.0); + let (val, overflow) = Self::from_bigint_with_overflow(l * r); + (!overflow).then(|| val) + } + + /// Performs wrapping division + #[inline] + pub fn wrapping_div(self, other: Self) -> Self { + let l = BigInt::from_signed_bytes_le(&self.0); + let r = BigInt::from_signed_bytes_le(&other.0); + Self::from_bigint_with_overflow(l / r).0 + } + + /// Performs checked division + #[inline] + pub fn checked_div(self, other: Self) -> Option { + let l = BigInt::from_signed_bytes_le(&self.0); + let r = BigInt::from_signed_bytes_le(&other.0); + let (val, overflow) = Self::from_bigint_with_overflow(l / r); + (!overflow).then(|| val) + } + + /// Performs wrapping remainder + #[inline] + pub fn wrapping_rem(self, other: Self) -> Self { + let l = BigInt::from_signed_bytes_le(&self.0); + let r = BigInt::from_signed_bytes_le(&other.0); + Self::from_bigint_with_overflow(l % r).0 + } + + /// Performs checked division + #[inline] + pub fn checked_rem(self, other: Self) -> Option { + if other.0 == [0; 32] { + return None; + } + + let l = BigInt::from_signed_bytes_le(&self.0); + let r = BigInt::from_signed_bytes_le(&other.0); + let (val, overflow) = Self::from_bigint_with_overflow(l % r); + (!overflow).then(|| val) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use num::BigInt; + use rand::{thread_rng, Rng}; + + #[test] + fn test_signed_cmp() { + let a = i256::from_parts(i128::MAX as u128, 12); + let b = i256::from_parts(i128::MIN as u128, 12); + assert!(a < b); + + let a = i256::from_parts(i128::MAX as u128, 12); + let b = i256::from_parts(i128::MIN as u128, -12); + assert!(a > b); + } + + #[test] + fn test_i256() { + let mut rng = thread_rng(); + + for _ in 0..1000 { + let mut l = [0_u8; 32]; + let len = rng.gen_range(0..32); + l.iter_mut().take(len).for_each(|x| *x = rng.gen()); + + let mut r = [0_u8; 32]; + let len = rng.gen_range(0..32); + r.iter_mut().take(len).for_each(|x| *x = rng.gen()); + + let il = i256::from_le_bytes(l); + let ir = i256::from_le_bytes(r); + + let bl = BigInt::from_signed_bytes_le(&l); + let br = BigInt::from_signed_bytes_le(&r); + + // Comparison + assert_eq!(il.cmp(&ir), bl.cmp(&br), "{} cmp {}", bl, br); + + // Addition + let actual = il.wrapping_add(ir); + let (expected, overflow) = + i256::from_bigint_with_overflow(bl.clone() + br.clone()); + assert_eq!(actual, expected); + + let checked = il.checked_add(ir); + match overflow { + true => assert!(checked.is_none()), + false => assert_eq!(checked.unwrap(), actual), + } + + // Subtraction + 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()); + + let checked = il.checked_sub(ir); + match overflow { + true => assert!(checked.is_none()), + false => assert_eq!(checked.unwrap(), actual), + } + } + } +} diff --git a/arrow-buffer/src/lib.rs b/arrow-buffer/src/lib.rs index 74d2bd5ec86..13d44e4d57f 100644 --- a/arrow-buffer/src/lib.rs +++ b/arrow-buffer/src/lib.rs @@ -21,8 +21,10 @@ pub mod alloc; pub mod buffer; pub use buffer::{Buffer, MutableBuffer}; +mod bigint; mod bytes; mod native; +pub use bigint::i256; pub use native::*; mod util; diff --git a/arrow-buffer/src/native.rs b/arrow-buffer/src/native.rs index 90855872d18..2812a9b4740 100644 --- a/arrow-buffer/src/native.rs +++ b/arrow-buffer/src/native.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use crate::i256; use half::f16; mod private { @@ -269,6 +270,8 @@ impl ArrowNativeType for f32 {} impl private::Sealed for f32 {} impl ArrowNativeType for f64 {} impl private::Sealed for f64 {} +impl private::Sealed for i256 {} +impl ArrowNativeType for i256 {} /// Allows conversion from supported Arrow types to a byte slice. pub trait ToByteSlice { From 09deb53c5f04acdbff939c4c69a10e4206d1f528 Mon Sep 17 00:00:00 2001 From: Raphael Taylor-Davies Date: Mon, 26 Sep 2022 20:12:15 +0100 Subject: [PATCH 2/9] RAT --- arrow-buffer/src/bigint.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/arrow-buffer/src/bigint.rs b/arrow-buffer/src/bigint.rs index e444a56b6d6..76d23ee67e7 100644 --- a/arrow-buffer/src/bigint.rs +++ b/arrow-buffer/src/bigint.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + use num::BigInt; use std::cmp::Ordering; From dad5d16fc1a3db5ae7e54e87145973b1175b9951 Mon Sep 17 00:00:00 2001 From: Raphael Taylor-Davies Date: Mon, 26 Sep 2022 21:02:29 +0100 Subject: [PATCH 3/9] Fix doc comments --- arrow-buffer/src/bigint.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arrow-buffer/src/bigint.rs b/arrow-buffer/src/bigint.rs index 76d23ee67e7..69ae5c46942 100644 --- a/arrow-buffer/src/bigint.rs +++ b/arrow-buffer/src/bigint.rs @@ -73,7 +73,7 @@ impl i256 { ) } - /// Create an i256 from the provided low and high i128 + /// Create an i256 from the provided low u128 and high i128 #[inline] fn from_parts(low: u128, high: i128) -> Self { let mut t = [0; 32]; @@ -125,7 +125,7 @@ impl i256 { Some(Self::from_parts(low, high)) } - /// Performs wrapping addition + /// Performs wrapping subtraction #[inline] pub fn wrapping_sub(self, other: Self) -> Self { let (left_low, left_high) = self.as_parts(); @@ -136,7 +136,7 @@ impl i256 { Self::from_parts(low, high) } - /// Performs checked addition + /// Performs checked subtraction #[inline] pub fn checked_sub(self, other: Self) -> Option { let (left_low, left_high) = self.as_parts(); @@ -189,7 +189,7 @@ impl i256 { Self::from_bigint_with_overflow(l % r).0 } - /// Performs checked division + /// Performs checked remainder #[inline] pub fn checked_rem(self, other: Self) -> Option { if other.0 == [0; 32] { From 6a8b52b528bbf4605f8190c4481e3e0e0313920d Mon Sep 17 00:00:00 2001 From: Raphael Taylor-Davies Date: Tue, 27 Sep 2022 09:04:31 +0100 Subject: [PATCH 4/9] Store as parts --- arrow-buffer/src/bigint.rs | 123 ++++++++++++++++++------------------- 1 file changed, 60 insertions(+), 63 deletions(-) diff --git a/arrow-buffer/src/bigint.rs b/arrow-buffer/src/bigint.rs index 69ae5c46942..0b586bbcfb5 100644 --- a/arrow-buffer/src/bigint.rs +++ b/arrow-buffer/src/bigint.rs @@ -21,7 +21,10 @@ use std::cmp::Ordering; /// A signed 256-bit integer #[allow(non_camel_case_types)] #[derive(Copy, Clone, Default, Eq, PartialEq, Hash)] -pub struct i256([u8; 32]); +pub struct i256 { + low: u128, + high: i128, +} impl std::fmt::Debug for i256 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -31,7 +34,7 @@ impl std::fmt::Debug for i256 { impl std::fmt::Display for i256 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", BigInt::from_signed_bytes_le(&self.0)) + write!(f, "{}", BigInt::from_signed_bytes_le(&self.to_le_bytes())) } } @@ -45,43 +48,47 @@ impl Ord for i256 { fn cmp(&self, other: &Self) -> Ordering { // This is 25x faster than using a variable length encoding such // as BigInt as it avoids allocation and branching - let (left_low, left_high) = self.as_parts(); - let (right_low, right_high) = other.as_parts(); - left_high.cmp(&right_high).then(left_low.cmp(&right_low)) + self.high.cmp(&other.high).then(self.low.cmp(&other.low)) } } impl i256 { + /// The additive identity for this integer type, i.e. `0`. + pub const ZERO: Self = i256 { low: 0, high: 0 }; + + /// The multiplicative identity for this integer type, i.e. `1`. + pub const ONE: Self = i256 { low: 1, high: 0 }; + + /// The multiplicative inverse for this integer type, i.e. `-1`. + pub const MINUS_ONE: Self = i256 { + low: u128::MAX, + high: -1, + }; + /// Create an integer value from its representation as a byte array in little-endian. #[inline] pub fn from_le_bytes(b: [u8; 32]) -> Self { - Self(b) - } - - /// Return the memory representation of this integer as a byte array in little-endian byte order. - #[inline] - pub fn to_le_bytes(self) -> [u8; 32] { - self.0 + Self { + high: i128::from_le_bytes(b[16..32].try_into().unwrap()), + low: u128::from_le_bytes(b[0..16].try_into().unwrap()), + } } - /// Returns this i256 as a low u128 and high i128 + /// Create an i256 from the provided low u128 and high i128 #[inline] - fn as_parts(self) -> (u128, i128) { - ( - u128::from_le_bytes(self.0[0..16].try_into().unwrap()), - i128::from_le_bytes(self.0[16..32].try_into().unwrap()), - ) + fn from_parts(low: u128, high: i128) -> Self { + Self { low, high } } - /// Create an i256 from the provided low u128 and high i128 + /// Return the memory representation of this integer as a byte array in little-endian byte order. #[inline] - fn from_parts(low: u128, high: i128) -> Self { + pub fn to_le_bytes(self) -> [u8; 32] { let mut t = [0; 32]; let t_low: &mut [u8; 16] = (&mut t[0..16]).try_into().unwrap(); - *t_low = low.to_le_bytes(); + *t_low = self.low.to_le_bytes(); let t_high: &mut [u8; 16] = (&mut t[16..32]).try_into().unwrap(); - *t_high = high.to_le_bytes(); - Self(t) + *t_high = self.high.to_le_bytes(); + t } /// Create an i256 from the provided [`BigInt`] returning a bool indicating @@ -96,70 +103,60 @@ impl i256 { [0; 32] }; bytes[0..v_bytes.len()].copy_from_slice(&v_bytes[..v_bytes.len()]); - (Self(bytes), false) + (Self::from_le_bytes(bytes), false) + } + Ordering::Equal => (Self::from_le_bytes(v_bytes.try_into().unwrap()), false), + Ordering::Greater => { + (Self::from_le_bytes(v_bytes[..32].try_into().unwrap()), true) } - Ordering::Equal => (Self(v_bytes.try_into().unwrap()), false), - Ordering::Greater => (Self(v_bytes[..32].try_into().unwrap()), true), } } /// Performs wrapping addition #[inline] pub fn wrapping_add(self, other: Self) -> Self { - let (left_low, left_high) = self.as_parts(); - let (right_low, right_high) = other.as_parts(); - - let (low, carry) = left_low.overflowing_add(right_low); - let high = left_high.wrapping_add(right_high).wrapping_add(carry as _); - Self::from_parts(low, high) + let (low, carry) = self.low.overflowing_add(other.low); + let high = self.high.wrapping_add(other.high).wrapping_add(carry as _); + Self { low, high } } /// Performs checked addition #[inline] pub fn checked_add(self, other: Self) -> Option { - let (left_low, left_high) = self.as_parts(); - let (right_low, right_high) = other.as_parts(); - - let (low, carry) = left_low.overflowing_add(right_low); - let high = left_high.checked_add(right_high)?.checked_add(carry as _)?; - Some(Self::from_parts(low, high)) + let (low, carry) = self.low.overflowing_add(other.low); + let high = self.high.checked_add(other.high)?.checked_add(carry as _)?; + Some(Self { low, high }) } /// Performs wrapping subtraction #[inline] pub fn wrapping_sub(self, other: Self) -> Self { - let (left_low, left_high) = self.as_parts(); - let (right_low, right_high) = other.as_parts(); - - let (low, carry) = left_low.overflowing_sub(right_low); - let high = left_high.wrapping_sub(right_high).wrapping_sub(carry as _); - Self::from_parts(low, high) + let (low, carry) = self.low.overflowing_sub(other.low); + let high = self.high.wrapping_sub(other.high).wrapping_sub(carry as _); + Self { low, high } } /// Performs checked subtraction #[inline] pub fn checked_sub(self, other: Self) -> Option { - let (left_low, left_high) = self.as_parts(); - let (right_low, right_high) = other.as_parts(); - - let (low, carry) = left_low.overflowing_sub(right_low); - let high = left_high.checked_sub(right_high)?.checked_sub(carry as _)?; - Some(Self::from_parts(low, high)) + let (low, carry) = self.low.overflowing_sub(other.low); + let high = self.high.checked_sub(other.high)?.checked_sub(carry as _)?; + Some(Self { low, high }) } /// Performs wrapping multiplication #[inline] pub fn wrapping_mul(self, other: Self) -> Self { - let l = BigInt::from_signed_bytes_le(&self.0); - let r = BigInt::from_signed_bytes_le(&other.0); + let l = BigInt::from_signed_bytes_le(&self.to_le_bytes()); + let r = BigInt::from_signed_bytes_le(&other.to_le_bytes()); Self::from_bigint_with_overflow(l * r).0 } /// Performs checked multiplication #[inline] pub fn checked_mul(self, other: Self) -> Option { - let l = BigInt::from_signed_bytes_le(&self.0); - let r = BigInt::from_signed_bytes_le(&other.0); + let l = BigInt::from_signed_bytes_le(&self.to_le_bytes()); + let r = BigInt::from_signed_bytes_le(&other.to_le_bytes()); let (val, overflow) = Self::from_bigint_with_overflow(l * r); (!overflow).then(|| val) } @@ -167,16 +164,16 @@ impl i256 { /// Performs wrapping division #[inline] pub fn wrapping_div(self, other: Self) -> Self { - let l = BigInt::from_signed_bytes_le(&self.0); - let r = BigInt::from_signed_bytes_le(&other.0); + let l = BigInt::from_signed_bytes_le(&self.to_le_bytes()); + let r = BigInt::from_signed_bytes_le(&other.to_le_bytes()); Self::from_bigint_with_overflow(l / r).0 } /// Performs checked division #[inline] pub fn checked_div(self, other: Self) -> Option { - let l = BigInt::from_signed_bytes_le(&self.0); - let r = BigInt::from_signed_bytes_le(&other.0); + let l = BigInt::from_signed_bytes_le(&self.to_le_bytes()); + let r = BigInt::from_signed_bytes_le(&other.to_le_bytes()); let (val, overflow) = Self::from_bigint_with_overflow(l / r); (!overflow).then(|| val) } @@ -184,20 +181,20 @@ impl i256 { /// Performs wrapping remainder #[inline] pub fn wrapping_rem(self, other: Self) -> Self { - let l = BigInt::from_signed_bytes_le(&self.0); - let r = BigInt::from_signed_bytes_le(&other.0); + let l = BigInt::from_signed_bytes_le(&self.to_le_bytes()); + let r = BigInt::from_signed_bytes_le(&other.to_le_bytes()); Self::from_bigint_with_overflow(l % r).0 } /// Performs checked remainder #[inline] pub fn checked_rem(self, other: Self) -> Option { - if other.0 == [0; 32] { + if other == Self::ZERO { return None; } - let l = BigInt::from_signed_bytes_le(&self.0); - let r = BigInt::from_signed_bytes_le(&other.0); + let l = BigInt::from_signed_bytes_le(&self.to_le_bytes()); + let r = BigInt::from_signed_bytes_le(&other.to_le_bytes()); let (val, overflow) = Self::from_bigint_with_overflow(l % r); (!overflow).then(|| val) } From 2bf6870b786776d4c16b22a94e612ce5f152f82b Mon Sep 17 00:00:00 2001 From: Raphael Taylor-Davies Date: Tue, 27 Sep 2022 10:18:00 +0100 Subject: [PATCH 5/9] Custom multiply implementation --- arrow-buffer/src/bigint.rs | 73 ++++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 7 deletions(-) diff --git a/arrow-buffer/src/bigint.rs b/arrow-buffer/src/bigint.rs index 0b586bbcfb5..94528f61373 100644 --- a/arrow-buffer/src/bigint.rs +++ b/arrow-buffer/src/bigint.rs @@ -147,18 +147,31 @@ impl i256 { /// Performs wrapping multiplication #[inline] pub fn wrapping_mul(self, other: Self) -> Self { - let l = BigInt::from_signed_bytes_le(&self.to_le_bytes()); - let r = BigInt::from_signed_bytes_le(&other.to_le_bytes()); - Self::from_bigint_with_overflow(l * r).0 + let (low, high) = mulx(self.low, other.low); + + // Compute the high multiples, only impacting the high 128-bits + let hl = self.high.wrapping_mul(other.low as i128); + let lh = (self.low as i128).wrapping_mul(other.high); + + Self { + low, + high: (high as i128).wrapping_add(hl).wrapping_add(lh), + } } /// Performs checked multiplication #[inline] pub fn checked_mul(self, other: Self) -> Option { - let l = BigInt::from_signed_bytes_le(&self.to_le_bytes()); - let r = BigInt::from_signed_bytes_le(&other.to_le_bytes()); - let (val, overflow) = Self::from_bigint_with_overflow(l * r); - (!overflow).then(|| val) + let (low, high) = mulx(self.low, other.low); + + // Compute the high multiples, only impacting the high 128-bits + let hl = self.high.checked_mul(other.low as i128)?; + let lh = (self.low as i128).checked_mul(other.high)?; + + Some(Self { + low, + high: (high as i128).checked_add(hl)?.checked_add(lh)?, + }) } /// Performs wrapping division @@ -200,6 +213,40 @@ impl i256 { } } +#[inline] +fn mulx(a: u128, b: u128) -> (u128, u128) { + let split = |a: u128| (a & (u64::MAX as u128), a >> 64); + + const MASK: u128 = u64::MAX as _; + + let (a_low, a_high) = split(a); + let (b_low, b_high) = split(b); + + // Carry stores the upper 64-bits of low and lower 64-bits of high + let (mut low, mut carry) = split(a_low * b_low); + carry += a_high * b_low; + + // Update low and high with corresponding parts of carry + low += carry << 64; + let mut high = carry >> 64; + + // Update carry with overflow from low + carry = low >> 64; + low &= MASK; + + // Perform multiply including overflow from low + carry += b_high * a_low; + + // Update low and high with values from carry + low += carry << 64; + high += carry >> 64; + + // Perform 4th multiplication + high += a_high * b_high; + + (low, high) +} + #[cfg(test)] mod tests { use super::*; @@ -262,6 +309,18 @@ mod tests { true => assert!(checked.is_none()), false => assert_eq!(checked.unwrap(), actual), } + + // Multiplication + let actual = il.wrapping_mul(ir); + let (expected, overflow) = + i256::from_bigint_with_overflow(bl.clone() * br.clone()); + assert_eq!(actual.to_string(), expected.to_string()); + + let checked = il.checked_mul(ir); + match overflow { + true => assert!(checked.is_none()), + false => assert_eq!(checked.unwrap(), actual), + } } } } From f18453e1aee84d37895c84852922ceccdadeacfb Mon Sep 17 00:00:00 2001 From: Raphael Taylor-Davies Date: Tue, 27 Sep 2022 11:05:49 +0100 Subject: [PATCH 6/9] Make from_parts public --- arrow-buffer/src/bigint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arrow-buffer/src/bigint.rs b/arrow-buffer/src/bigint.rs index 94528f61373..a20580f8b26 100644 --- a/arrow-buffer/src/bigint.rs +++ b/arrow-buffer/src/bigint.rs @@ -76,7 +76,7 @@ impl i256 { /// Create an i256 from the provided low u128 and high i128 #[inline] - fn from_parts(low: u128, high: i128) -> Self { + pub fn from_parts(low: u128, high: i128) -> Self { Self { low, high } } From e207ae62ff886a415860418e649bb5e743119f17 Mon Sep 17 00:00:00 2001 From: Raphael Taylor-Davies Date: Mon, 3 Oct 2022 09:44:43 +0100 Subject: [PATCH 7/9] Document mulx --- arrow-buffer/src/bigint.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arrow-buffer/src/bigint.rs b/arrow-buffer/src/bigint.rs index a20580f8b26..c29c50f0607 100644 --- a/arrow-buffer/src/bigint.rs +++ b/arrow-buffer/src/bigint.rs @@ -213,6 +213,11 @@ impl i256 { } } +/// Performs an unsigned multiplication of `a * b` returning a tuple of +/// `(low, high)` where `low` contains the lower 128-bits of the result +/// and `high` the higher 128-bits +/// +/// 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); From 743d2f7fb9c3d264aae2d06b7f8d16ed219513c8 Mon Sep 17 00:00:00 2001 From: Raphael Taylor-Davies Date: Mon, 3 Oct 2022 11:05:33 +0100 Subject: [PATCH 8/9] Remove branch from to_i128 --- arrow-buffer/src/bigint.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/arrow-buffer/src/bigint.rs b/arrow-buffer/src/bigint.rs index 51f5883f157..11e88cfc632 100644 --- a/arrow-buffer/src/bigint.rs +++ b/arrow-buffer/src/bigint.rs @@ -88,14 +88,13 @@ impl i256 { /// Converts this `i256` into an `i128` returning `None` if this would result /// in truncation/overflow pub fn to_i128(self) -> Option { - let is_negative = match self.high { - 0 => false, - -1 => true, - _ => return None - }; - - let top_bit_set = (self.low & (1 << 127)) != 0; - (is_negative == top_bit_set).then(|| self.low as 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) } /// Return the memory representation of this integer as a byte array in little-endian byte order. From 48ec6b97f96d5e70c1de15ba068c8c6771452321 Mon Sep 17 00:00:00 2001 From: Raphael Taylor-Davies Date: Mon, 3 Oct 2022 11:23:22 +0100 Subject: [PATCH 9/9] Clippy --- arrow-buffer/src/bigint.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arrow-buffer/src/bigint.rs b/arrow-buffer/src/bigint.rs index 11e88cfc632..a08d280ca88 100644 --- a/arrow-buffer/src/bigint.rs +++ b/arrow-buffer/src/bigint.rs @@ -205,7 +205,7 @@ impl i256 { let l = BigInt::from_signed_bytes_le(&self.to_le_bytes()); let r = BigInt::from_signed_bytes_le(&other.to_le_bytes()); let (val, overflow) = Self::from_bigint_with_overflow(l / r); - (!overflow).then(|| val) + (!overflow).then_some(val) } /// Performs wrapping remainder @@ -226,7 +226,7 @@ impl i256 { let l = BigInt::from_signed_bytes_le(&self.to_le_bytes()); let r = BigInt::from_signed_bytes_le(&other.to_le_bytes()); let (val, overflow) = Self::from_bigint_with_overflow(l % r); - (!overflow).then(|| val) + (!overflow).then_some(val) } }