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

上一篇博文简单介绍了大整数的表示方法,这次开始介绍一些基本的算法。

      ★ 初始化和清除

编写大整数函数的出发点是bignum结构的初始化和清除,在其他大部分算法当中,这两个算法都会用到。

对于给定的bignum结构,初始化有两种情况:一是仅仅把bignum结构的dp指向NULL,二是初始化的时候顺便分配一定的动态内存,并让dp指针指向这块内存。其实我本来打算只用第二种方式进行初始化,不过考虑到初始内存可能分配过多导致内存浪费,于是决定两种方式一起使用。第一种方式的优点是在后面编程中你不必考虑到底要预先分配多少内存,内存分配的工作交给bn_grow函数去做,减少了工作量,不过它的缺陷也比较明显,如果一开始的内存分配过少,在后面可能需要多次的内存分配来增加精度,效率降低,要知道堆上面的操作还是挺费时间的。

如果bignum结构中的dp指针已经被初始化(NULL或者是指向某一分配好的内存),就必须对结构的其他成员进行初始化。bignum初始化后默认是0,所以sign = 1, used = 0。如果dp为NULL,alloc = 0,否则alloc = 分配的数位(nlimbs)。

为了避免内存的无限制分配,这里给数位设了一个上限值BN_MAX_LIMB, 在头文件中定义是:

#define BN_MAX_LIMB                   25600

也就是说bignum结构的最大数位不能超过25600,在32位环境下,每一个数位长度32bit,所以bignum最大长度是819200bit,对于加密算法来说绰绰有余了。

为了便于移植,有如下宏被定义(参考了PolarSSL的定义,比较好理解):

#define ciL                           (sizeof(bn_digit))       //chars in limb (每个数位的字节大小)
        #define biL                           (ciL << 3)                    //bits in limb (每个数位的bit大小)
        #define biLH                        (ciL << 2)                    //half bits in limb  (每个数位的bit大小的一半,这个后面的模运算会用到)

#define BN_MALLOC                     malloc
        #define BN_FREE                             free

/**
 * bignum结构初始化,未分配内存。
 */
void bn_init(bignum *x)
{
    if(x != NULL)
    {
        x->sign = 1;
        x->alloc = 0;
        x->used = 0;
        x->dp = NULL;
    }
}

/**
 * bignum结构初始化并分配内存。
 * 若初始化失败(内存分配错误),返回BN_MEMORY_ALLOCATE_FAIL
 * 若超出数位上限,返回BN_EXCEED_MAX_LIMB
 */
int bn_init_size(bignum *x, size_t nlimbs)
{
    bn_digit *p;

    if(nlimbs > BN_MAX_LIMB)
        return BN_EXCEED_MAX_LIMB;

    if(x != NULL)
    {
        p = (bn_digit *)BN_MALLOC(nlimbs * ciL);
        if(p == NULL) return BN_MEMORY_ALLOCATE_FAIL;

        memset(p, 0x00, nlimbs * ciL);    //初始化后bignum默认为0,所以必须把p指向的内存的所有值置为0.

        x->sign = 1;
        x->alloc = nlimbs;
        x->used = 0;
        x->dp = p;
    }

    return 0;
}

当不再需要使用bignum时,就应该使用bn_free函数及时释放其分配的内存。bn_free函数有两个作用:一是清除bignum的数位和其他成员,这样即使不小心使用了已经清除的bignum也不会出问题,二是释放已经分配的内存。

将已经清除的bignum的dp置为NULL,这样后面再次调用该函数时函数就可以检测到该bignum已经被清除,避免多次内存释放。

void bn_free(bignum *x)
{
    if(x != NULL)
    {
        if(x->dp != NULL)
        {
            memset(x->dp, 0x00, x->alloc * ciL);
            BN_FREE(x->dp);
        }
        x->sign = 1;
        x->alloc = 0;
        x->used = 0;
        x->dp = NULL;
    }
}

      ★ 精度增加

      当在bignum中存储一个值的时候,必须要有足够多的位数才能无损地存放一个完整的大整数。如果alloc足够大,那么只需要增加used即可,否则就需要重新分配内存增大alloc以满足要求。

bn_grow函数首先检查需要的数位(nlimbs)是否大于alloc,如果不是,那无需增加精度,函数调用结束,这样就避免了内存重新分配,节约了时间。如果nlimbs大于alloc,那么就要重新分配一段长度为nlimbs的内存空间,初始化为0后把原来dp指向的内存中的内容复制到新的空间中,并且释放原先dp指向的内存。

int bn_grow(bignum *x, size_t nlimbs)
{
    bn_digit *p;

    if(nlimbs > BN_MAX_LIMB)
        return BN_EXCEED_MAX_LIMB;

    if(nlimbs > x->alloc)
    {
        p = (bn_digit *)BN_MALLOC(nlimbs * ciL);
        if(p == NULL) return BN_MEMORY_ALLOCATE_FAIL;

        memset(p, 0, nlimbs * ciL);

        if(x->dp != NULL)    //这里判断bignum结构的dp之前是否分配了内存,如果有分配才进行复制和内存释放操作。
        {
            memcpy(p, x->dp, x->alloc * ciL);
            memset(x->dp, 0, x->alloc * ciL);
            BN_FREE(x->dp);
        }
        x->dp = p;
        x->alloc = nlimbs;
    }

    return 0;
}

      ★ 复制操作

这个就很简单了,把bignum y复制给bignum x,注意这里的x->dp指向新的内存,而不是指向y->dp。

int bn_copy(bignum *x, const bignum *y)
{
    int ret;
    size_t nlimbs;

    if(x == y) return 0;     //x == y,表明 x 跟 y 是同一个数,此时无需任何操作,直接返回。注意这里 x 跟 y 都是指针

    x->sign = y->sign;
    x->used = y->used;
    nlimbs = (y->used == 0) ? 1 : y->used;     //如果 y 为0,默认给 x 分配一位的空间
    BN_CHECK(bn_grow(x, nlimbs));

    memset(x->dp, 0x00, x->alloc * ciL);     //初始化内存

    if(y->dp != NULL && y->used > 0)
        memcpy(x->dp, y->dp, y->used * ciL);

clean:

    return ret;
}

      ★ 单精度赋值操作

有时候我们想把bignum设置成一个相对较小的单精度数,比如1, 2^32 - 1等等。 通过分配1个数位的内存,把单精度数赋值给数组的首个单元即可。在这里我默认的单精度数是无符号的(即非负整数),如果想要赋值一个负数,可以直接把sign设为-1即可。

int bn_set_word(bignum *x, const bn_digit word)
{
    int ret;

    BN_CHECK(bn_grow(x, 1));         //分配一个数位的内存
    memset(x->dp, 0, x->alloc * ciL);   //初始化

    x->dp[0] = word;
    x->used = (word != 0) ? 1 : 0;
    x->sign = 1;      //如果是负数,在函数调用完后把sign设成-1即可

clean:

    return ret;
}

      ★ 交换操作

       这函数的作用很简单,就是交换两个大整数 x 和 y,具体的实现原理也很简单,交换结构体中的每个成员即可。

void bn_swap(bignum *x, bignum *y)
{
    int tmp_sign;
    size_t tmp_alloc;
    size_t tmp_used;
    bn_digit *tmp_dp;

    tmp_sign = x->sign;
    tmp_alloc = x->alloc;
    tmp_used = x->used;
    tmp_dp = x->dp;

    y->sign = x->sign;
    y->alloc = x->alloc;
    y->used = x->used;
    y->dp = x->dp;

    x->sign = tmp_sign;
    x->alloc = tmp_alloc;
    x->used = tmp_used;
    x->dp = tmp_dp;
}

      ★ 总结

       本文介绍的这些算法都是大整数库中的最基本算法,虽然很简单,但它们都是其他算法的基石,后面的算法将会频繁使用到这些算法。下一篇文章将谈谈如何比较两个大整数,包括绝对值比较,有符号大整数比较和大整数与单精度数的比较。

时间: 2024-08-20 15:10:11

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

字符串+数组模拟大整数乘法

C/C++中存在精度问题,很难做到大整数的加法和乘法操作,这里给出大整数的模拟乘法运算. 模拟原理: 模拟每一个位的值进行相乘,并使其加到对应的位置上,最后保证每一位的数都小于10,即从尾到头扫描一遍进位即可. 主要代码: k=(mx-i)+(mbx-j);//相乘后的位置 c[k-1]+=sum%10; c[k]+=sum/10; for(t=1;t<=k;++t){//保证每一位上的数都小于10 if(c[t]>=10){ //cout<<c[t]<<"

大整数算法[00] 概述

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

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

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

大整数算法[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 相当于一个基,比如十进制下,

算法---大整数相加

原文:算法---大整数相加 开通博客开始第一次写发表算法博客.深知一半算法考试都是用C,C++,由于大四开始到今年毕业工作到现在一直从事C#开发,C++用得很少了.链表,指针也只知道一个概念了.用得没以前熟练了.所以后续更新的算法题我都是基于C#语法的.算法主要体现的是解题思路.跟题目一样,本次算法主要实现大数据相加. 解题思路: 1. 将大数据存储到一个链表中,C#中用List<int>来存储,每个节点表示每一位的数字. {1,2,3,4,5} =>12345 和{9,6,5,9,5}

大整数算法

本文主要整理了几个常用的大整数的算法:大整数加法大整数乘法大整数阶乘大整数幂其实大体的思路都差不多,都是用数组来存储大整数.以下的代码仅仅实现功能,并没有充分详细的参数判断,在实际运用中,肯定是需要考虑的. 大整数相加 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] Comba乘法(原理)

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

大整数算法[04] 位操作

上一篇文章介绍了大整数的比较操作,今天来谈谈和位相关的操作. ★ 引子         在大整数的表示和相关定义这篇文章中讲到了大整数是如何表示的.为了方便后面的讲解,这里先按照前面的定义,给出一个大整数的例子(32位系统下,每一个数位长度为32比特): 假设有一 bignum x,十进制值为 1134924633606254832,对应的十六进制值为 0xFC00FF0F0F0F0F0,那么按照大整数的表示方法, 2^32 进制就表示成(每一位用逗号隔开): (264245232,404232

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

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