大整数算法[04] 位操作

上一篇文章介绍了大整数的比较操作,今天来谈谈和位相关的操作。

引子

        大整数的表示和相关定义这篇文章中讲到了大整数是如何表示的。为了方便后面的讲解,这里先按照前面的定义,给出一个大整数的例子(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 的结构,比如使用十进制,那么操作起来就是相当麻烦,这再一次说明编程时二进制思维是很重要嘀。下一篇文章会介绍大整数的移位操作。

时间: 2024-07-30 07:16:52

大整数算法[04] 位操作的相关文章

大整数算法[01] 大整数的表示和相关定义

★ 相关的数据类型定义 在干正事之前,先定义好各种数据类型还是很有必要的,避免在以后的编码中引起混乱. uintX   X位无符号整形,如uint32表示32位无符号整形 intX    X位有符号整形,如int32表示32位有符号整形 基本数据类型定义: #ifdef _MSC_VER            typedef __int8              int8;            typedef __int16             int16;            typ

大整数算法[00] 概述

★ 为啥要做这个 早在大一的时候,我便对密码学产生兴趣.那时在计算机导论后面看到RSA加密的计算原理,觉得十分有趣,于是就很想自己实现一个RSA加密,不过我很快就放弃了,因为实在搞不定那超长的整数计算.C里面最长的整数类型也就64位,对于动辄就1024位的RSA密钥,这连个零头都没有.为了完成这个目标,我便开始琢磨着弄一个用来计算大整数的库.原本我也打算使用别人已经写好的大数库,不过最终还是决定自己搞一个,因为凡是效率高速度快的大数库 (OpenSSL, Crypto++, GMP),要么使用的

大整数算法[08] Comba乘法(原理)

★ 引子          原本打算一篇文章讲完,后来发现篇幅会很大,所以拆成两部分,先讲原理,再讲实现.实现的话相对复杂,要用到内联汇编,要考虑不同平台等等. 在大整数计算中,乘法是非常重要的,因为在公钥密码学中模幂运算要频繁使用乘法,所以乘法的性能会直接影响到模幂运算的效率.下面将会介绍两种乘法:基线乘法和 Comba 乘法,尽管他们的原理和计算看起来十分类似,而且算法的时间复杂度都是 O(n^2),但是他们的效率差别是很大的. ★ 基线乘法 (Baseline Multiplication

大整数算法[11] Karatsuba乘法

★ 引子         前面两篇介绍了 Comba 乘法,最后提到当输入的规模很大时,所需的计算时间会急剧增长,因为 Comba 乘法的时间复杂度仍然是 O(n^2).想要打破乘法中 O(n^2) 的限制,需要从一个完全不同的角度来看待乘法.在下面的乘法算法中,需要使用 x 和 y 这两个大整数的多项式基表达式 f(x) 和 g(x) 来表示. 令 f(x) = a * x + b,g(x) = c * x + d,h(x) = f(x) * g(x).这里的 x 相当于一个基,比如十进制下,

大整数算法[13] 单数位乘法

★ 引子 最近在折腾 wxWidgets,同时拖延症又犯了,所以中断了好久.这次来讲讲单数位乘法,前面讲到 Comba 和 Karatsuba 乘法,这两个算法适合用来处理比较大的整数,但是对于一个大整数和一个单精度数相乘,其效果反而会不好,因为计算量过多.实际上单数位乘法只是基线乘法的一个特例,不存在嵌套循环进位,因此可以通过优化减少计算量.另外与完整的乘法不同的是,单数位乘法不需要什么临时变量存储和内存分配(目标精度增加除外). ★ 算法思路         单数位乘法类似于计算 12345

大整数算法

本文主要整理了几个常用的大整数的算法:大整数加法大整数乘法大整数阶乘大整数幂其实大体的思路都差不多,都是用数组来存储大整数.以下的代码仅仅实现功能,并没有充分详细的参数判断,在实际运用中,肯定是需要考虑的. 大整数相加 1 #include <stdio.h> 2 #include <string.h> 3 #define N 1000 4 void get_num(int *array) 5 { 6 char str[N] = {'\0'}; 7 int loop = 0; 8

大整数算法[08] 有符号加法和减法

★ 引子 前面几篇文章介绍了比较操作,绝对值加法和绝对值减法,现在就可以利用这几个算法构建有符号数的加减算法. ★ 有符号数加法            有符号数的加法分成两种情况:同号和异号. 1.  如果两个数同号,则执行绝对值加法,如果两个数为非负数,则结果为非负数:如果两个数都是负数,则结果也为负数. 2.  如果两个数异号,则要执行绝对值减法,用绝对值较大的数去减绝对值较小的数.最终结果 z 的符号由 x 和 y 的绝对值大小决定:如果 x 的绝对值大于或等于 y,则 z 的符号与 x

大整数算法[02] 基本的操作(维护算法)

上一篇博文简单介绍了大整数的表示方法,这次开始介绍一些基本的算法.       ★ 初始化和清除 编写大整数函数的出发点是bignum结构的初始化和清除,在其他大部分算法当中,这两个算法都会用到. 对于给定的bignum结构,初始化有两种情况:一是仅仅把bignum结构的dp指向NULL,二是初始化的时候顺便分配一定的动态内存,并让dp指针指向这块内存.其实我本来打算只用第二种方式进行初始化,不过考虑到初始内存可能分配过多导致内存浪费,于是决定两种方式一起使用.第一种方式的优点是在后面编程中你不

[转]大整数算法[11] Karatsuba乘法

★ 引子         前面两篇介绍了 Comba 乘法,最后提到当输入的规模很大时,所需的计算时间会急剧增长,因为 Comba 乘法的时间复杂度仍然是 O(n^2).想要打破乘法中 O(n^2) 的限制,需要从一个完全不同的角度来看待乘法.在下面的乘法算法中,需要使用 x 和 y 这两个大整数的多项式基表达式 f(x) 和 g(x) 来表示. 令 f(x) = a * x + b,g(x) = c * x + d,h(x) = f(x) * g(x).这里的 x 相当于一个基,比如十进制下,