大数运算

說明

基於記憶體的有效運用,程式語言中規定了各種不同的資料型態,也因此變數所可以表達的最大整數受到限制,例如123456789123456789這樣的 整數就不可能儲存在long變數中(例如C/C++等),我們稱這為long數,這邊翻為超長整數(避免與資料型態的長整數翻譯混淆),或俗稱大數運算。

解法

一個變數無法表示超長整數,則就使用多個變數,當然這使用陣列最為方便,假設程式語言的最大資料型態可以儲存至65535的數好了,為了計算方便及符合使用十進位制的習慣,讓每一個陣列元素可以儲存四個位數,也就是0到9999的數,例如:


很多人問到如何計算像50!這樣的問題,解法就是使用程式中的乘法函式,至於要算到多大,就看需求了。

如果您使用的是Java,那麼在java.lang下有BigInteger與BigDecimal可以直接進行大數運算。

由於使用陣列來儲存數值,關於數值在運算時的加減乘除等各種運算、位數的進位或借位就必須自行定義,加、減、乘都是由低位數開始運算,而除法則是由高位數開始運算,這邊直接提供加減乘除運算的函式供作參考,以下的N為陣列長度。

實作:C     Java     Python  

  • C
void add(int *a, int *b, int *c) { 

int i, carry = 0;



for(i = N - 1; i >= 0; i--) {

c[i] = a[i] + b[i] + carry;

if(c[i] < 10000)

carry = 0;

else { // 進位

c[i] = c[i] - 10000;

carry = 1;

}

}

}



void subtract(int *a, int *b, int *c) {

int i, borrow = 0;



for(i = N - 1; i >= 0; i--) {

c[i] = a[i] - b[i] - borrow;

if(c[i] >= 0)

borrow = 0;

else { // 借位

c[i] = c[i] + 10000;

borrow = 1;

}

}

}



void multiply(int *a, int b, int *c) { // b 為乘數

int i, tmp, carry = 0;



for(i = N - 1; i >=0; i--) {

tmp = a[i] * b + carry;

c[i] = tmp % 10000;

carry = tmp / 10000;

}

}



void divide(int *a, int b, int *c) { // b 為除數

int i, tmp, remain = 0;



for(i = 0; i < N; i++) {

tmp = a[i] + remain;

c[i] = tmp / b;

remain = (tmp % b) * 10000;

}

}

 

  • Java
public class BigNumber {

public static int[] add(int[] a, int[] b) {

int carry = 0;

int[] c = new int[a.length];



for(int i = a.length - 1; i >= 0; i--) {

c[i] = a[i] + b[i] + carry;

if(c[i] < 10000)

carry = 0;

else { // 進位

c[i] -= 10000;

carry = 1;

}

}



return c;

}



public static int[] subtract(int[] a, int[] b) {

int borrow = 0;

int[] c = new int[a.length];



for(int i = a.length - 1; i >= 0; i--) {

c[i] = a[i] - b[i] - borrow;

if(c[i] >= 0)

borrow = 0;

else { // 借位

c[i] += 10000;

borrow = 1;

}

}



return c;

}



public static int[] multiply(int[] a, int b) { // b 為乘數

int carry = 0;

int[] c = new int[a.length];



for(int i = a.length - 1; i >=0; i--) {

int tmp = a[i] * b + carry;

c[i] = tmp % 10000;

carry = tmp / 10000;

}



return c;

}



public static int[] divide(int[] a, int b) { // b 為除數

int remain = 0;

int[] c = new int[a.length];



for(int i = 0; i < a.length; i++) {

int tmp = a[i] + remain;

c[i] = tmp / b;

remain = (tmp % b) * 10000;

}



return c;

}



public static void main(String[] args) {

int[] a = {1234, 5678, 9910, 1923, 1124};

int[] b = {1234, 5678, 9910, 1923, 1124};

int[] c = BigNumber.add(a, b);



for(int i = 0; i < c.length; i++) {

System.out.print(c[i]);

}

System.out.println();

}

}

 

  • Python
class BigNumber:

@staticmethod

def add(a, b):

carry = 0

c = [0] * len(a)

for i in range(len(a) - 1, -1, -1):

c[i] = a[i] + b[i] + carry

if c[i] < 10000:

carry = 0

else:

c[i] -= 10000

carry = 1

return c



@staticmethod

def subtract(a, b):

borrow = 0

c = [0] * len(a)

for i in range(len(a) - 1, -1, -1):

c[i] = a[i] - b[i] - borrow

if c[i] >= 0:

borrow = 0

else:

c[i] = c[i] + 10000

borrow = 1

return c



@staticmethod

def multiply(a, b):

carry = 0

c = [0] * len(a)

for i in range(len(a) - 1, -1, -1):

tmp = a[i] * b + carry

c[i] = tmp % 10000

carry = tmp // 10000

return c



@staticmethod

def divide(a, b):

remain = 0

c = [0] * len(a)

for i in range(len(a)):

tmp = a[i] + remain

c[i] = tmp // b

remain = (tmp % b) * 10000

return c



a = [1234, 5678, 9910, 1923, 1124]

b = [1234, 5678, 9910, 1923, 1124]

c = BigNumber.divide(a, 3)

for i in range(len(c)):

print(c[i], end="")

print()


--------------------------------------------------------------------------------------------------------------------------


RSA依赖大数运算,目前主流RSA算法都建立在512位到1024位的
大数运算之上,所以我们在现阶段首先需要掌握1024位的大数
运算原理。


大多数的编译器只能支持到64位的整数运算,即我们在运算中
所使用的整数必须小于等于64位,即:0xffffffffffffffff
也就是18446744073709551615,这远远达不到RSA的需要,于是
需要专门建立大数运算库来解决这一问题。

最简单的办法是将大数当作字符串进行处理,也就是将大数用
10进制字符数组进行表示,然后模拟人们手工进行“竖式计算”
的过程编写其加减乘除函数。但是这样做效率很低,因为1024
位的大数其10进制数字个数就有数百个,对于任何一种运算,
都需要在两个有数百个元素的数组空间上做多重循环,还需要
许多额外的空间存放计算的进位退位标志及中间结果。当然其
优点是算法符合人们的日常习惯,易于理解。

另一种思路是将大数当作一个二进制流进行处理,使用各种移
位和逻辑操作来进行加减乘除运算,但是这样做代码设计非常
复杂,可读性很低,难以理解也难以调试。

于是俺琢磨了一种介于两者之间的思路:

将大数看作一个n进制数组,对于目前的32位系统而言n可以取
值为2的32次方,即0x10000000,假如将一个1024位的大数转
化成0x10000000进制,它就变成了32位,而每一位的取值范围
就不是0-1或0-9,而是0-0xffffffff。我们正好可以用一个无
符号长整数来表示这一数值。所以1024位的大数就是一个有32
个元素的unsigned long数组。而且0x100000000进制的数组排列
与2进制流对于计算机来说,实际上是一回事,但是我们完全
可以针对unsigned long数组进行“竖式计算”,而循环规模
被降低到了32次之内,并且算法很容易理解。

例如大数18446744073709551615,等于“ffffffff ffffffff”,
它就相当于10进制的“99”:有两位,每位都是ffffffff。
而大数18446744073709551616,等于“00000001 00000000
00000000”,它就相当于10进制的“100”:有三位,第一位是
1,其它两位是0。如果我们要计算18446744073709551616
-18446744073709551615,就类似于100-99:
 
  00000001 00000000 00000000
-           ffffffff ffffffff
-----------------------------
=        0         0        1


所以,可以声明大数类如下:
/****************************************************************/
//大数运算库头文件:BigInt.h
//作者:[email protected]
//版本:1.0 (2003.4.26)
//说明:适用于MFC
/****************************************************************/

#define BI_MAXLEN 40
#define DEC 10
#define HEX 16

class CBigInt
{
public:
   int m_nSign;    //记录大数的符号,支持负值运算
   int m_nLength;  //记录0x10000000进制的位数,0-40之间,相当于2进制的0-1280位
   unsigned long m_ulvalue[BI_MAXLEN];   //记录每一位的“数字”

   CBigInt();
   ~CBigInt();


//将大数赋值为另一个大数    
   CBigInt& Mov(CBigInt& A);

//将大数赋值为编译器能够理解的任何整形常数或变量  
   CBigInt& Mov(unsigned __int64 A);

//比较两个大数大小
   int Cmp(CBigInt& A);

//计算两个大数的和
   CBigInt Add(CBigInt& A);

//重载函数以支持大数与普通整数相加
   CBigInt Add(long A);

//计算两个大数的差
   CBigInt Sub(CBigInt& A);

//重载函数以支持大数与普通整数相减
   CBigInt Sub(long A);

//计算两个大数的积
   CBigInt Mul(CBigInt& A);

//重载函数以支持大数与普通整数相乘
   CBigInt Mul(long A);

//计算两个大数的商
   CBigInt Div(CBigInt& A);

//重载函数以支持大数与普通整数相除
   CBigInt Div(long A);

//计算两个大数相除的余数
   CBigInt Mod(CBigInt& A);

//重载函数以支持大数与普通整数相除求模
   long Mod(long A);

//将输入的10进制或16进制字符串转换成大数
   int InPutFromStr(CString& str, const unsigned int system);

//将大数按10进制或16进制格式输出到字符串
   int OutPutToStr(CString& str, const unsigned int system);

//欧几里德算法求:Y=X.Euc(A),使满足:YX mod A = 1
   CBigInt Euc(CBigInt& A);

//蒙哥马利算法求:Y=X.Mon(A,B),使满足:X^A mod B = Y
   CBigInt Mon(CBigInt& A, CBigInt& B);
};


注意以上函数的声明格式,完全遵循普通整数运算的习惯,例如大数
Y=X+Z 相当于 Y.Mov(X.(Add(Z)),这样似乎没有Mov(Y,Add(X,Z))
看起来舒服,但是一旦我们重载运算符“=”为“Mov”,“+”为“Add”,
则Y.Mov(X.(Add(Z))的形式就等价于 Y=X+Z。

俺不知道其他编程语言里是否支持运算浮重载,至少这样定义函数格式
在C++里可以带来很大的方便。


下面让我们来实现大数类的主要成员函数:
/****************************************************************/
//大数运算库源文件:BigInt.cpp
//作者:[email protected]
//版本:1.0 (2003.4.26)
//说明:适用于MFC
/****************************************************************/

#include "stdafx.h"
#include "BigInt.h"

//初始化大数为0
CBigInt::CBigInt()
{
m_nSign=1;
m_nLength=1;
for(int i=0;i<BI_MAXLEN;i++)m_ulvalue[i]=0;
}

//采用缺省的解构函数
CBigInt::~CBigInt()
{
}

//大数比较,如果大数A位数比大数B多,当然A>B
//如果位数相同,则从高位开始比较,直到分出大小
int CBigInt::Cmp(CBigInt& A)
{
if(m_nLength>A.m_nLength)return 1;
if(m_nLength<A.m_nLength)return -1;
for(int i=m_nLength-1;i>=0;i--)
{
if(m_ulvalue[i]>A.m_ulvalue[i])return 1;
if(m_ulvalue[i]<A.m_ulvalue[i])return -1;
}
return 0;
}

//照搬参数的各属性
CBigInt& CBigInt::Mov(CBigInt& A)
{
m_nLength=A.m_nLength;
for(int i=0;i<BI_MAXLEN;i++)m_ulvalue[i]=A.m_ulvalue[i];
return *this;
}

//大数相加
//调用形式:N.Add(A),返回值:N+A
//若两大数符号相同,其值相加,否则改变参数符号再调用大数相减函数
/******************************************************************/
例如:
     A  B  C
+       D  E
--------------
= S  F  G  H

其中,若C+E<=0xffffffff,则H=C+E,carry(进位标志)=0
     若C+E>0xffffffff,则H=C+E-0x100000000,carry=1

     若B+D+carry<=0xfffffff,则G=B+D,carry=0      
     若B+D+carry>0xfffffff,则G=B+D+carry-0x10000000,carry=1

     若carry=0,则F=A,S=0
     若carry=1,A<0xfffffff,则F=A+1,S=0
     若carry=1,A=0xfffffff,则F=0,S=1
/*****************************************************************/
CBigInt CBigInt::Add(CBigInt& A)
{
CBigInt X;
if(X.m_nSign==A.m_nSign)
{
X.Mov(*this);
int carry=0;
       unsigned __int64 sum=0;
       if(X.m_nLength<A.m_nLength)X.m_nLength=A.m_nLength;
for(int i=0;i<X.m_nLength;i++)
{
sum=A.m_ulvalue[i];
sum=sum+X.m_ulvalue[i]+carry;
X.m_ulvalue[i]=(unsigned long)sum;
if(sum>0xffffffff)carry=1;
else carry=0;
}
if(X.m_nLength<BI_MAXLEN)
{
X.m_ulvalue[X.m_nLength]=carry;
   X.m_nLength+=carry;
}
return X;
}
else{X.Mov(A);X.m_nSign=1-X.m_nSign;return Sub(X);}
}

//大数相减
//调用形式:N.Sub(A),返回值:N-A
//若两大数符号相同,其值相减,否则改变参数符号再调用大数相加函数
/******************************************************************/
例如:
     A  B  C
-       D  E
--------------
=    F  G  H

其中,若C>=E,则H=C-E,carry(借位标志)=0
     若C<E,则H=C-E+0x100000000,carry=1

     若B-carry>=D,则G=B-carry-D,carry=0      
     若B-carry<D,则G=B-carry-D+0x10000000,carry=1

     若carry=0,则F=A
     若carry=1,A>1,则F=A-1
     若carry=1,A=1,则F=0
/*****************************************************************/
CBigInt CBigInt::Sub(CBigInt& A)
{
CBigInt X;
if(m_nSign==A.m_nSign)
{
X.Mov(*this);
int cmp=X.Cmp(A);
if(cmp==0){X.Mov(0);return X;}
int len,carry=0;
unsigned __int64 num;
unsigned long *s,*d;
       if(cmp>0)
               {
                       s=X.m_ulvalue;
                       d=A.m_ulvalue;
                       len=X.m_nLength;
               }
       if(cmp<0)
               {
                       s=A.m_ulvalue;
                       d=X.m_ulvalue;
                       len=A.m_nLength;
                       X.m_nSign=1-X.m_nSign;
               }
       for(int i=0;i<len;i++)
{
if((s[i]-carry)>=d[i])
{
X.m_ulvalue[i]=s[i]-carry-d[i];
carry=0;
}
else
{
num=0x100000000+s[i];
X.m_ulvalue[i]=(unsigned long)(num-carry-d[i]);
carry=1;
}
}
while(X.m_ulvalue[len-1]==0)len--;
X.m_nLength=len;
return X;
}
else{X.Mov(A);X.m_nSign=1-X.m_nSign;return Add(X);}
}

//大数相乘
//调用形式:N.Mul(A),返回值:N*A
/******************************************************************/
例如:
        A  B  C
*          D  E
----------------
=    S  F  G  H
+ T  I  J  K
----------------
= U  V  L  M  N

其中,SFGH=ABC*E,TIJK=ABC*D

而对于:
     A  B  C
*          E
-------------
= S  F  G  H    

其中,若C*E<=0xffffffff,则H=C*E,carry(进位标志)=0
     若C*E>0xffffffff,则H=(C*E)&0xffffffff
       carry=(C*E)/0xffffffff
     若B*E+carry<=0xffffffff,则G=B*E+carry,carry=0
     若B*E+carry>0xffffffff,则G=(B*E+carry)&0xffffffff
       carry=(B*E+carry)/0xffffffff
     若A*E+carry<=0xffffffff,则F=A*E+carry,carry=0
     若A*E+carry>0xffffffff,则F=(A*E+carry)&0xffffffff
       carry=(A*E+carry)/0xffffffff
     S=carry
/*****************************************************************/
CBigInt CBigInt::Mul(CBigInt& A)
{
CBigInt X,Y;
unsigned __int64 mul;
       unsigned long carry;
       for(int i=0;i<A.m_nLength;i++)
{
Y.m_nLength=m_nLength;
carry=0;
for(int j=0;j<m_nLength;j++)
{
mul=m_ulvalue[j];
mul=mul*A.m_ulvalue[i]+carry;
Y.m_ulvalue[j]=(unsigned long)mul;
carry=(unsigned long)(mul>>32);
}
if(carry&&(Y.m_nLength<BI_MAXLEN))
               {
                       Y.m_nLength++;
                       Y.m_ulvalue[Y.m_nLength-1]=carry;
               }
if(Y.m_nLength<BI_MAXLEN-i)
{
Y.m_nLength+=i;
       for(int k=Y.m_nLength-1;k>=i;k--)Y.m_ulvalue[k]=Y.m_ulvalue[k-i];
       for(k=0;k<i;k++)Y.m_ulvalue[k]=0;
}
X.Mov(X.Add(Y));
}
if(m_nSign+A.m_nSign==1)X.m_nSign=0;
else X.m_nSign=1;
return X;
}

//大数相除
//调用形式:N.Div(A),返回值:N/A
//除法的关键在于“试商”,然后就变成了乘法和减法
//这里将被除数与除数的试商转化成了被除数最高位与除数最高位的试商
CBigInt CBigInt::Div(CBigInt& A)
{
CBigInt X,Y,Z;
int len;
unsigned __int64 num,div;
unsigned long carry=0;
Y.Mov(*this);
while(Y.Cmp(A)>0)
{      
if(Y.m_ulvalue[Y.m_nLength-1]>A.m_ulvalue[A.m_nLength-1])
{
len=Y.m_nLength-A.m_nLength;
div=Y.m_ulvalue[Y.m_nLength-1]/(A.m_ulvalue[A.m_nLength-1]+1);
}
else if(Y.m_nLength>A.m_nLength)
{
len=Y.m_nLength-A.m_nLength-1;
num=Y.m_ulvalue[Y.m_nLength-1];
num=(num<<32)+Y.m_ulvalue[Y.m_nLength-2];
if(A.m_ulvalue[A.m_nLength-1]==0xffffffff)div=(num>>32);
else div=num/(A.m_ulvalue[A.m_nLength-1]+1);
}
else
{
                       X.Mov(X.Add(1));
break;
}
               Z.Mov(div);
Z.m_nLength+=len;
for(int i=Z.m_nLength-1;i>=len;i--)Z.m_ulvalue[i]=Z.m_ulvalue[i-len];
for(i=0;i<len;i++)Z.m_ulvalue[i]=0;
X.Mov(X.Add(Z));
Z.Mov(Z.Mul(A));
Y.Mov(Y.Sub(Z));
}
if(Y.Cmp(A)==0)X.Mov(X.Add(1));
if(m_nSign+A.m_nSign==1)X.m_nSign=0;
else X.m_nSign=1;
return X;
}

//大数求模
//调用形式:N.Mod(A),返回值:N%A
//求模与求商原理相同
CBigInt CBigInt::Mod(CBigInt& A)
{
CBigInt X,Y;
int len;
unsigned __int64 num,div;
unsigned long carry=0;
X.Mov(*this);
while(X.Cmp(A)>0)
{      
if(X.m_ulvalue[X.m_nLength-1]>A.m_ulvalue[A.m_nLength-1])
{
len=X.m_nLength-A.m_nLength;
div=X.m_ulvalue[X.m_nLength-1]/(A.m_ulvalue[A.m_nLength-1]+1);
}
else if(X.m_nLength>A.m_nLength)
{
len=X.m_nLength-A.m_nLength-1;
num=X.m_ulvalue[X.m_nLength-1];
num=(num<<32)+X.m_ulvalue[X.m_nLength-2];
if(A.m_ulvalue[A.m_nLength-1]==0xffffffff)div=(num>>32);
else div=num/(A.m_ulvalue[A.m_nLength-1]+1);
}
else
{
X.Mov(X.Sub(A));
break;
}
               Y.Mov(div);
Y.Mov(Y.Mul(A));
Y.m_nLength+=len;
for(int i=Y.m_nLength-1;i>=len;i--)Y.m_ulvalue[i]=Y.m_ulvalue[i-len];
for(i=0;i<len;i++)Y.m_ulvalue[i]=0;
X.Mov(X.Sub(Y));
}
if(X.Cmp(A)==0)X.Mov(0);
return X;
}


//暂时只给出了十进制字符串的转化
int CBigInt::InPutFromStr(CString& str, const unsigned int system=DEC)
{
       int len=str.GetLength();
Mov(0);
for(int i=0;i<len;i++)
       {
             Mov(Mul(system));
int k=str[i]-48;
Mov(Add(k));
 }
 return 0;
}

//暂时只给出了十进制字符串的转化
int CBigInt::OutPutToStr(CString& str, const unsigned int system=DEC)
{
str="";
char ch;
CBigInt X;
X.Mov(*this);
while(X.m_ulvalue[X.m_nLength-1]>0)
{
ch=X.Mod(system)+48;
str.Insert(0,ch);
       X.Mov(X.Div(system));
}
return 0;
}

//欧几里德算法求:Y=X.Euc(A),使满足:YX mod A=1
//相当于对不定方程ax-by=1求最小整数解
//实际上就是初中学过的辗转相除法
/********************************************************************/
例如:11x-49y=1,求x

           11 x  -  49 y  =   1      a)
49%11=5 ->  11 x  -   5 y  =   1      b)
11%5 =1 ->     x  -   5 y  =   1      c)

令y=1  代入c)式  得x=6
令x=6  代入b)式  得y=13
令y=13 代入a)式  得x=58  
/********************************************************************/
CBigInt CBigInt::Euc(CBigInt& A)
{
CBigInt X,Y;
X.Mov(*this);
Y.Mov(A);
if((X.m_nLength==1)&&(X.m_ulvalue[0]==1))return X;
if((Y.m_nLength==1)&&(Y.m_ulvalue[0]==1)){X.Mov(X.Sub(1));return X;}
if(X.Cmp(Y)==1)X.Mov(X.Mod(Y));
else Y.Mov(Y.Mod(X));
X.Mov(X.Euc(Y));
       Y.Mov(*this);
if(Y.Cmp(A)==1)
{
X.Mov(X.Mul(Y));
X.Mov(X.Sub(1));
X.Mov(X.Div(A));
}
else
{
X.Mov(X.Mul(A));
X.Mov(X.Add(1));
X.Mov(X.Div(Y));
}
return X;
}

//蒙哥马利算法求:Y=X.Mon(A,B),使满足:X^A mod B=Y
//俺估计就是高中学过的反复平方法
CBigInt CBigInt::Mon(CBigInt& A, CBigInt& B)
{
CBigInt X,Y,Z;
X.Mov(1);
Y.Mov(*this);
       Z.Mov(A);
while((Z.m_nLength!=1)||Z.m_ulvalue[0])
{
if(Z.m_ulvalue[0]&1)
{
Z.Mov(Z.Sub(1));
X.Mov(X.Mul(Y));
X.Mov(X.Mod(B));
}
else
{
Z.Mov(Z.Div(2));
Y.Mov(Y.Mul(Y));
Y.Mov(Y.Mod(B));
}
}
       return X;
}


最后需要说明的是因为在VC里面存在一个__int64类型可以
用来计算进位与借位值,所以将大数当作0x100000000进制
进行运算是可能的,而在其他编译系统中如果不存在64位
整形,则可以采用0x40000000进制,由于在0x40000000
进制中,对任何两个“数字”进行四则运算,结果都在
0x3fffffff*03fffffff之间,小于0xffffffff,都可以用
一个32位无符号整数来表示。事实上《楚汉棋缘》采用的
freelip大数库正是运用了0x40000000进制来表示大数的,
所以其反汇编后大数的值在内存中表现出来有些“奇怪”。

你可能感兴趣的:(java,c,算法,System,div,编译器)