This is an automated email from the ASF dual-hosted git repository.
mbrobbel 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 0fbc18db65 improve performance of i256 to f64 (#8041)
0fbc18db65 is described below
commit 0fbc18db6580939d3bb8ef89cf14a7cc2b732ef1
Author: Congxian Qiu <[email protected]>
AuthorDate: Tue Sep 16 14:55:19 2025 +0800
improve performance of i256 to f64 (#8041)
# Which issue does this PR close?
- Closes #8013 .
# What changes are included in this PR?
Improve the logic `i256` to `f64` by
1. Erase all the leading sign bit in `i256`, this will right-pad 0,
2. Treat the convert logic as `i256` = `the f64 described as the left 64
bit after step 1` * `the scale( = pow(2, 192 - the bit count we erased
in the first step)` -- the fraction bits in f64 is 52, i64 is enought
for it.
2.1 Convert the left 64 bits in the first step to f64
2.2 Multiply the scale (`pow(2, 192 - the bit count we erased in the
first step)`)
# Are these changes tested?
Covered by the existing tests, and add some more tests
# Are there any user-facing changes?
No
---------
Co-authored-by: Matthijs Brobbel <[email protected]>
---
arrow-buffer/benches/i256.rs | 9 +++++++-
arrow-buffer/src/bigint/mod.rs | 50 ++++++++++++++++++++++++++++++++----------
2 files changed, 47 insertions(+), 12 deletions(-)
diff --git a/arrow-buffer/benches/i256.rs b/arrow-buffer/benches/i256.rs
index 7dec226bbc..11aaa83c8d 100644
--- a/arrow-buffer/benches/i256.rs
+++ b/arrow-buffer/benches/i256.rs
@@ -17,6 +17,7 @@
use arrow_buffer::i256;
use criterion::*;
+use num::cast::ToPrimitive;
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use std::{hint, str::FromStr};
@@ -36,13 +37,19 @@ fn criterion_benchmark(c: &mut Criterion) {
i256::MAX,
];
- for number in numbers {
+ for number in numbers.iter() {
let t = hint::black_box(number.to_string());
c.bench_function(&format!("i256_parse({t})"), |b| {
b.iter(|| i256::from_str(&t).unwrap());
});
}
+ for number in numbers.iter() {
+ c.bench_function(&format!("i256_to_f64({number})"), |b| {
+ b.iter(|| (*number).to_f64().unwrap())
+ });
+ }
+
let mut rng = StdRng::seed_from_u64(42);
let numerators: Vec<_> = (0..SIZE)
diff --git a/arrow-buffer/src/bigint/mod.rs b/arrow-buffer/src/bigint/mod.rs
index 92f11d68d3..d7959a71ab 100644
--- a/arrow-buffer/src/bigint/mod.rs
+++ b/arrow-buffer/src/bigint/mod.rs
@@ -586,6 +586,25 @@ impl i256 {
pub const fn is_positive(self) -> bool {
self.high.is_positive() || self.high == 0 && self.low != 0
}
+
+ fn leading_zeros(&self) -> u32 {
+ match self.high {
+ 0 => u128::BITS + self.low.leading_zeros(),
+ _ => self.high.leading_zeros(),
+ }
+ }
+
+ fn redundant_leading_sign_bits_i256(n: i256) -> u8 {
+ let mask = n >> 255; // all ones or all zeros
+ ((n ^ mask).leading_zeros() - 1) as u8 // we only need one sign bit
+ }
+
+ fn i256_to_f64(input: i256) -> f64 {
+ let k = i256::redundant_leading_sign_bits_i256(input);
+ let n = input << k; // left-justify (no redundant sign bits)
+ let n = (n.high >> 64) as i64; // throw away the lower 192 bits
+ (n as f64) * f64::powi(2.0, 192 - (k as i32)) // convert to f64 and
scale it, as we left-shift k bit previous, so we need to scale it by 2^(192-k)
+ }
}
/// Temporary workaround due to lack of stable const array slicing
@@ -822,19 +841,14 @@ impl ToPrimitive for i256 {
}
fn to_f64(&self) -> Option<f64> {
- let mag = if let Some(u) = self.checked_abs() {
- let (low, high) = u.to_parts();
- (high as f64) * 2_f64.powi(128) + (low as f64)
- } else {
- // self == MIN
- 2_f64.powi(255)
- };
- if *self < i256::ZERO {
- Some(-mag)
- } else {
- Some(mag)
+ match *self {
+ Self::MIN => Some(-2_f64.powi(255)),
+ Self::ZERO => Some(0f64),
+ Self::ONE => Some(1f64),
+ n => Some(Self::i256_to_f64(n)),
}
}
+
fn to_u64(&self) -> Option<u64> {
let as_i128 = self.low as i128;
@@ -1286,6 +1300,20 @@ mod tests {
let v = i256::from_i128(-123456789012345678i128);
assert_eq!(v.to_f64().unwrap(), -123456789012345678.0);
+
+ let v = i256::from_string("0").unwrap();
+ assert_eq!(v.to_f64().unwrap(), 0.0);
+
+ let v = i256::from_string("1").unwrap();
+ assert_eq!(v.to_f64().unwrap(), 1.0);
+
+ let mut rng = rng();
+ for _ in 0..10 {
+ let f64_value =
+ (rng.random_range(i128::MIN..i128::MAX) as f64) *
rng.random_range(0.0..1.0);
+ let big = i256::from_f64(f64_value).unwrap();
+ assert_eq!(big.to_f64().unwrap(), f64_value);
+ }
}
#[test]