about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2022-01-15 11:28:22 +0100
committerGitHub <noreply@github.com>2022-01-15 11:28:22 +0100
commitf511360fd29e426c4a24ebde038f5bfbfcf93f88 (patch)
treebdb41b553f0998d6a77ff21c7877a7546477b74b
parent38c22af0153cf8f920c01ef04493e8878401fd18 (diff)
parent0589cace8c943d40a60b7356d8c772baf2879cee (diff)
downloadrust-f511360fd29e426c4a24ebde038f5bfbfcf93f88.tar.gz
rust-f511360fd29e426c4a24ebde038f5bfbfcf93f88.zip
Rollup merge of #92747 - swenson:bignum-bit-length-optimization, r=scottmcm
Simplification of BigNum::bit_length

As indicated in the comment, the BigNum::bit_length function could be
optimized by using CLZ, which is often a single instruction instead a
loop.

I think the code is also simpler now without the loop.

I added some additional tests for Big8x3 and Big32x40 to ensure that
there were no regressions.
-rw-r--r--library/core/src/num/bignum.rs21
-rw-r--r--library/core/tests/num/bignum.rs35
2 files changed, 41 insertions, 15 deletions
diff --git a/library/core/src/num/bignum.rs b/library/core/src/num/bignum.rs
index 8a06a098882..98d8a8a1d74 100644
--- a/library/core/src/num/bignum.rs
+++ b/library/core/src/num/bignum.rs
@@ -158,24 +158,15 @@ macro_rules! define_bignum {
             /// Returns the number of bits necessary to represent this value. Note that zero
             /// is considered to need 0 bits.
             pub fn bit_length(&self) -> usize {
-                // Skip over the most significant digits which are zero.
+                let digitbits = <$ty>::BITS as usize;
                 let digits = self.digits();
-                let zeros = digits.iter().rev().take_while(|&&x| x == 0).count();
-                let end = digits.len() - zeros;
-                let nonzero = &digits[..end];
-
-                if nonzero.is_empty() {
+                // Find the most significant non-zero digit.
+                let msd = digits.iter().rposition(|&x| x != 0);
+                match msd {
+                    Some(msd) => msd * digitbits + digits[msd].log2() as usize + 1,
                     // There are no non-zero digits, i.e., the number is zero.
-                    return 0;
-                }
-                // This could be optimized with leading_zeros() and bit shifts, but that's
-                // probably not worth the hassle.
-                let digitbits = <$ty>::BITS as usize;
-                let mut i = nonzero.len() * digitbits - 1;
-                while self.get_bit(i) == 0 {
-                    i -= 1;
+                    _ => 0,
                 }
-                i + 1
             }
 
             /// Adds `other` to itself and returns its own mutable reference.
diff --git a/library/core/tests/num/bignum.rs b/library/core/tests/num/bignum.rs
index 1457064cc8d..416e7cea7a6 100644
--- a/library/core/tests/num/bignum.rs
+++ b/library/core/tests/num/bignum.rs
@@ -1,4 +1,5 @@
 use core::num::bignum::tests::Big8x3 as Big;
+use core::num::bignum::Big32x40;
 
 #[test]
 #[should_panic]
@@ -215,6 +216,16 @@ fn test_get_bit_out_of_range() {
 
 #[test]
 fn test_bit_length() {
+    for i in 0..8 * 3 {
+        // 010000...000
+        assert_eq!(Big::from_small(1).mul_pow2(i).bit_length(), i + 1);
+    }
+    for i in 1..8 * 3 - 1 {
+        // 010000...001
+        assert_eq!(Big::from_small(1).mul_pow2(i).add(&Big::from_small(1)).bit_length(), i + 1);
+        // 110000...000
+        assert_eq!(Big::from_small(3).mul_pow2(i).bit_length(), i + 2);
+    }
     assert_eq!(Big::from_small(0).bit_length(), 0);
     assert_eq!(Big::from_small(1).bit_length(), 1);
     assert_eq!(Big::from_small(5).bit_length(), 3);
@@ -224,6 +235,30 @@ fn test_bit_length() {
 }
 
 #[test]
+fn test_bit_length_32x40() {
+    for i in 0..32 * 40 {
+        // 010000...000
+        assert_eq!(Big32x40::from_small(1).mul_pow2(i).bit_length(), i + 1);
+    }
+    for i in 1..32 * 40 - 1 {
+        // 010000...001
+        assert_eq!(
+            Big32x40::from_small(1).mul_pow2(i).add(&Big32x40::from_small(1)).bit_length(),
+            i + 1
+        );
+        // 110000...000
+        assert_eq!(Big32x40::from_small(3).mul_pow2(i).bit_length(), i + 2);
+    }
+    assert_eq!(Big32x40::from_small(0).bit_length(), 0);
+    assert_eq!(Big32x40::from_small(1).bit_length(), 1);
+    assert_eq!(Big32x40::from_small(5).bit_length(), 3);
+    assert_eq!(Big32x40::from_small(0x18).bit_length(), 5);
+    assert_eq!(Big32x40::from_u64(0x4073).bit_length(), 15);
+    assert_eq!(Big32x40::from_u64(0xffffff).bit_length(), 24);
+    assert_eq!(Big32x40::from_u64(0xffffffffffffffff).bit_length(), 64);
+}
+
+#[test]
 fn test_ord() {
     assert!(Big::from_u64(0) < Big::from_u64(0xffffff));
     assert!(Big::from_u64(0x102) < Big::from_u64(0x201));