[英]BigUint binary complement
Example code : 示例代码:
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);
}
The above code is doing a binary complement of a BigUint
using a bit mask.上面的代码使用位掩码对
BigUint
进行二进制补码。 Questions:问题:
BigUint
doesn't include the !
BigUint
不包括!
operator (but the mask may be necessary anyway depending on how !
was defined).!
的定义方式,掩码可能仍然是必需的)。 More context with the problem I'm actually looking at: binary complement sequences有关我实际正在查看的问题的更多背景信息: 二进制补码序列
If you alternate multiplying by 3 and bit flipping a number some interesting sequences arise.如果您交替乘以 3 和位翻转数字,则会出现一些有趣的序列。 Example starting with 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)
One question is whether it reaches zero for all starting numbers or not.一个问题是它是否对所有起始数字都达到零。 Some meander around for quite a while before reaching zero (425720 takes 87,037,147,316 iterations to reach 0).
有些在达到零之前徘徊了很长一段时间(425720 需要 87,037,147,316 次迭代才能达到 0)。 Being able to compute this efficiently can help in answering these questions.
能够有效地计算这一点可以帮助回答这些问题。 Mostly I'm learning a bit more rust with this though.
大多数情况下,我通过这个学习了更多 rust 。
The best solution is probably to just do it yourself.最好的解决方案可能就是自己做。 You perform an allocation each time you create a
BigUint
which really slows down your program.每次创建
BigUint
时都会执行分配,这确实会减慢程序的速度。 Since we are not doing complex math, we can simplify most of this to a couple bitwise operations.由于我们不做复杂的数学运算,我们可以将其中的大部分简化为几个按位运算。
After a little bit of tinkering, here is how I implemented it.经过一些修补,这就是我实现它的方式。 For convenience, I used the unstable nightly feature
bigint_helper_methods
to allow for the carrying_add
function. This helped simplify the addition process.为了方便起见,我使用了不稳定的夜间功能
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
}
}
If you are looking for performance, num-bigint
probably isn't the best choice .如果您正在寻找性能,
num-bigint
可能不是最佳选择。 Everything that is really high-performance, though, seems to be GPL licensed.不过,所有真正高性能的东西似乎都获得了 GPL 许可。
Either way, here is a solution using the rug
library, which directly supports !
不管怎样,这里有一个使用
rug
库的解决方案,它直接支持!
(not), and seems to be really fast: (不是),而且似乎真的很快:
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
Note that not_assign
also inverts the sign bit.请注意,
not_assign
还会反转符号位。 We can remove that bit through the keep_bits_mut
function.我们可以通过
keep_bits_mut
function 删除该位。
For example, here is a version of your algorithm:例如,这是您的算法的一个版本:
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.