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]

Reply via email to