上一篇文章介绍了大整数的比较操作,今天来谈谈和位相关的操作。
★ 引子
在大整数的表示和相关定义这篇文章中讲到了大整数是如何表示的。为了方便后面的讲解,这里先按照前面的定义,给出一个大整数的例子(32位系统下,每一个数位长度为32比特):
假设有一 bignum x,十进制值为 1134924633606254832,对应的十六进制值为 0xFC00FF0F0F0F0F0,那么按照大整数的表示方法, 2^32 进制就表示成(每一位用逗号隔开):
(264245232,4042322160) 即:1134924633606254832 / (2^32) = 264245232, 1134924633606254832 mod (2^32) = 4042322160。
高位用二进制表示为:0000 1111 1100 0000 0000 1111 1111 0000
低位用二进制表示为:1111 0000 1111 0000 1111 0000 1111 0000
★ 返回指定下标 pos 的比特位
这里的下标(pos)是从大整数的最低为开始,并且以 0 作为起始值,例如上述 bignum x 中下标为 3 的比特位为 0,下标为 4 的比特位为 1。
给定一个 pos 值,要找出对应的比特位在哪里,可以先找到 pos 对应的比特位到底在大整数的哪一个数位中。比如当 pos = 38 时,pos / biL = 38 / 32 = 1,
biL (bits in limbs)为数位的比特长度,32 位系统下 biL 为 32。这样就知道 pos 对应的比特位在大整数 x 的第二个数位中(对应于下标为 1 的数组单元中)。找到了大概位置,就要确定在数位中的具体位置了,这可以通过做求余运算得到:pos % biL = 38 mod 32 = 6,这样将该数位右移 6 位后与 1 做一次逻辑与操作,即可得到 pos 对应的比特值为 1 (对应红色标明的那一位)。(0000 1111 1100 0000 0000 1111 1111 0000,1111 0000 1111 0000 1111 0000 1111 0000)
boolean bn_get_bit(const bignum *x, const size_t pos) { boolean bit; //pos is zero base number if(x->alloc * biL <= pos) return 0; bit = (x->dp[pos / biL] >> (pos & (biL - 1))) & 1; return bit; }
在 2 的补码运算中,计算 a mod (2^n) 等价于 a AND (2^n - 1)。
★ 给定下标值 pos 设置对应位置的比特位
和前面的原理一样,先找到 pos 对应的比特位的位置。假设 pos = 35,则 offset = pos / biL = 1, mod = pos / biL = 3。找到对应的位置后,要先把该位的值清空,具体的做法是:将 1 左移 mod = 3 位,然后取反,这时操作结果的二进制就会是 1111 1111 1111 1111 1111 1111 1111 0111,与大整数的第二个数位(对应数组下标为 1)进行与运算,便清空该位的值。然后将函数传入的 boolean 类型的 value 左移 mod = 3 位后与大整数的第二个数位做或运算即可把新的比特位设置好。
int bn_set_bit(bignum *x, const size_t pos, boolean value) { int ret = 0; size_t offset, mod; //pos is zero base number if(value != TRUE && value != FALSE) return BN_INVALID_INPUT; offset = pos / biL; mod = pos & (biL - 1); if(pos >= x->alloc * biL) { if(value == FALSE) return 0; //如果pos超过了分配的数位并且value为0,则无需操作,因为高位默认为0,否则需要增加精度。 BN_CHECK(bn_grow(x, offset + 1)); } x->dp[offset] &= ~((bn_digit)1 << mod); x->dp[offset] |= ((bn_digit)(value) << mod); x->used = x->alloc; //如果bignum的精度增加,则需要从最左边的数位开始往右检查有效数位,保证used值的正确。 bn_clamp(x); //压缩多余位 clean: return ret; }
★ 返回有效比特位的数量
有效比特位是指从左起第一个不为 0 的比特位开始到最右比特位为止的中间所有比特位。例如本文给出的例子,除最高位的前四个比特位之外,其他都是有效的比特位,有效比特位的数量是 60。按照前面所说的,可以很容易得到下面的算法:
size_t bn_msb(const bignum *x) { size_t i, j; i = x->used - 1; for(j = biL; j > 0; j--) if(((x->dp[i] >> (j - 1)) & 1) != 0) break; return biL * i + j; }
这个算法的意义在于可以计算出一个 bignum 的实际比特大小,虽然简单,但在后面很多算法中会多次用到。
★ 返回最低有效比特位前 0 的数量
最低有效比特位是指从右起第一个不为 0 的比特位,本例中 bignum x 的最低有效比特位就是位于低数位中从右起下标为 4 的比特位。前面 0 的数量为 4。于是可以很容易写出如下算法:
size_t bn_lsb(const bignum *x) { size_t i, j; for(i = 0; i < x->used; i++) { if(x->dp[i] != 0) { for(j = 0; j < biL; j++) { if(((x->dp[i] >> j) & 1) != 0) return i * biL + j; } } } return 0; }
对于一个整数 p(p > 0), 都可以通过如下的形式表示:p = q * 2^r,其中 q 是一个奇数,例如 36 = 9 * 2^2, r = 2。bn_lsb 算法的意义在于可以通过计算最低有效比特位前 0 的数量来计算 r 的值。这个算法虽然也很简单,但它会在后面计算最大公约数的算法中大显身手。
★ 返回 bignum 的字节大小
这里要注意的是:这个操作不是返回 bignum 占用了多少内存,例如对于 bignum x,即使一开始分配了 3 个单元的内存,其字节大小仍然是 8 byte。计算 bignum 的字节大小,只需要将 x 的比特大小加上 7 除以 8 即可。加上 7 的目的是避免 x 的比特位不是 8 的倍数时除完之后少了一个字节。
size_t bn_size(const bignum *x) { return ((bn_msb(x) + 7) >> 3); }
★ 总结
大整数位的相关操作原理不难,但是如果一开始没有定义好 bignum 的结构,比如使用十进制,那么操作起来就是相当麻烦,这再一次说明编程时二进制思维是很重要嘀。下一篇文章会介绍大整数的移位操作。