簡體   English   中英

BigUint 二進制補碼

[英]BigUint binary complement

示例代碼

use num_bigint::BigUint;
use num_traits::identities::One;

fn main() {
    // Example: 10001 (17) => 1110 (14)
    let n = BigUint::from(17u32);
    println!("{}", n);
    // BigUint doesn't support `!`
    //let n = !n;
    let mask = (BigUint::one() << n.bits()) - 1u32;
    let n = n ^ mask;
    println!("{}", n);
}

上面的代碼使用位掩碼對BigUint進行二進制補碼。 問題:

  1. 有沒有比使用掩碼更好的方法來進行二進制補碼? 似乎BigUint不包括! 運算符(但根據!的定義方式,掩碼可能仍然是必需的)。
  2. 如果沒有,是否有更好的方法來生成掩碼? (緩存掩碼有幫助,但可以使用大量 memory。)

有關我實際正在查看的問題的更多背景信息: 二進制補碼序列

如果您交替乘以 3 和位翻轉數字,則會出現一些有趣的序列。 以 3 開頭的示例:

0. 3 (11b)  => 3*3 = 9 (1001b) => bit complement is 6 (0110b)
1. 6 (110b)
2. 13 (1101b)
3. 24 (11000b)
4. 55 (110111b)
5. 90 (1011010b)
6. 241 (11110001b)
7. 300 (100101100b)
8. 123 (1111011b)
9. 142 (10001110b)
11. 85 (1010101b)
12. 0 (0b)

一個問題是它是否對所有起始數字都達到零。 有些在達到零之前徘徊了很長一段時間(425720 需要 87,037,147,316 次迭代才能達到 0)。 能夠有效地計算這一點可以幫助回答這些問題。 大多數情況下,我通過這個學習了更多 rust 。

最好的解決方案可能就是自己做。 每次創建BigUint時都會執行分配,這確實會減慢程序的速度。 由於我們不做復雜的數學運算,我們可以將其中的大部分簡化為幾個按位運算。

經過一些修補,這就是我實現它的方式。 為了方便起見,我使用了不穩定的夜間功能bigint_helper_methods來允許carrying_add function。這有助於簡化加法過程。

#[derive(Debug)]
pub struct BigUintHelper {
    words: Vec<u64>,
}

impl BigUintHelper {
    pub fn mul3_invert(&mut self) {
        let len = self.words.len();

        // Multiply everything by 3 by adding it to itself with a bit shift
        let mut carry = false;
        let mut prev_bit = 0;
        for word in &mut self.words[..len - 1] {
            let previous = *word;

            // Perform the addition operation
            let (next, next_carry) = previous.carrying_add((previous << 1) | prev_bit, carry);

            // Reset carried values for next round
            prev_bit = previous >> (u64::BITS - 1);
            carry = next_carry;

            // Invert the result as we go to avoid needing another pass
            *word = !next;
        }

        // Perform the last word seperatly since we may need to do the invert differently
        let previous = self.words[len - 1];
        let (next, next_carry) = previous.carrying_add((previous << 1) | prev_bit, carry);


        // Extra word from the combination of the carry bits
        match next_carry as u64 + (previous >> (u64::BITS - 1)) {
            0 => {
                // The carry was 0 so we do the normal process
                self.words[len - 1] = invert_bits(next);
                self.cleanup_end();
            }
            1 => {
                self.words[len - 1] = !next;
                // invert_bits(1) = 0
                self.cleanup_end();
            }
            2 => {
                self.words[len - 1] = !next;
                // invert_bits(2) = 1
                self.words.push(1);
            }
            _ => unreachable!(),
        }
    }

    /// Remove any high order words without any bits
    #[inline(always)]
    fn cleanup_end(&mut self) {
        while let Some(x) = self.words.pop() {
            if x != 0 {
                self.words.push(x);
                break;
            }
        }
    }

    /// Count how many rounds it takes to convert this value to 0.
    pub fn count_rounds(&mut self) -> u64 {
        let mut rounds = 0;

        while !self.words.is_empty() {
            self.mul3_invert();
            rounds += 1;
        }

        rounds
    }
}


impl From<u64> for BigUintHelper {
    fn from(x: u64) -> Self {
        BigUintHelper {
            words: vec![x],
        }
    }
}

#[inline(always)]
const fn invert_bits(x: u64) -> u64 {
    match x.leading_zeros() {
        0 => !x,
        y => ((1u64 << (u64::BITS - y)) - 1) ^ x
    }
}

Rust 游樂場

如果您正在尋找性能, num-bigint可能不是最佳選擇 不過,所有真正高性能的東西似乎都獲得了 GPL 許可。

不管怎樣,這里有一個使用rug庫的解決方案,它直接支持! (不是),而且似乎真的很快:

use rug::{ops::NotAssign, Integer};

fn main() {
    // Example: 10001 (17) => 1110 (14)
    let mut n = Integer::from(17u32);
    println!("{}", n);
    n.not_assign();
    n.keep_bits_mut(n.significant_bits() - 1);
    println!("{}", n);
}
17
14

請注意, not_assign還會反轉符號位。 我們可以通過keep_bits_mut function 刪除該位。


例如,這是您的算法的一個版本:

use rug::{ops::NotAssign, Integer};

fn step(n: &mut Integer) {
    *n *= 3;
    n.not_assign();
    n.keep_bits_mut(n.significant_bits() - 1);
}

fn main() {
    let mut n = Integer::from(3);
    println!("{}", n);
    while n != 0 {
        step(&mut n);
        println!("{}", n);
    }
}
3
6
13
24
55
90
241
300
123
142
85
0

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM