Big Number-大数运算

来源:互联网 发布:lua和javascript 编辑:程序博客网 时间:2024/04/26 07:27

Big Number

我个人觉得「 Big Number 」这个英文辞汇,听起来一点都不学术,何况,一般来说我们只讨论整数的部分。另外还有一种称呼是「 Big Integer 」,这听起来就正式多了。

言归正传。大数就是很大的数字,大到无法以一个简单的变数型态储存这个值。

一般来说, int 这个变数型态,记忆体大小为32 bit ,可以储存数值范围为-2^31 到2^31 - 1 的整数,大约是1 后面再接九个零;而long long 这个变数型态是64 bit 的,可以储存数值范围为-2^63 到2^63 - 1 的整数。另外还有unsigned 这个关键字,它能让原本的变数型态能够存入更大一点的正整数。

虽然int 、 long long 的数值大小已经够用了,但是人的欲望是无止尽的,总是想让电脑能够处理更大的数字、算得更精准。于是大数的技术就这样产生了。

资料结构

要让电脑存放这么大的数字,有个好方法就是使用阵列。阵列有很多格子,一个格子存一个数字;只要宣告1000 格大小的int 阵列,就可以存1000 位数了!至于一个int 变数,充其量也不过十位数而已──阵列能存放的数值大小,和int 相比之下,实在是多很多很多。

 
  1. struct  BigNumber
  2. {
  3.     int  array 1000 ];     //一个栏位存一个数字,可以存1000位数
  4.     bool  sign ;           //正负号
  5.     int  length ;          //位数
  6. };

通常我们习惯将低位数放在索引值( index )比较小的位置,高位数放在索引值比较大的位置。假设要存放680468975231245 。

每个人对阵列的思考模式不一样,像这里就是由左至右的,另外也有人觉得阵列是由右至左、由上至下、弯弯曲曲的、……。要怎么思考都是可以的,一以贯之就好啰。

阵列右端划上横线的格子,通常我喜欢存0 进去,这样子做运算的时候会比较方便;如果将横线的部分设成-1 ,在运算时会出现点麻烦,所以我不喜欢、也不建议这么做。

大数的各种功能

设计好了资料结构之后,接下来便要开始设计大数的各种功能,例如说显示大数,以及大数的四则运算。

为了让初学者能够清楚了解大数运算的方式,以下的程式码举要治繁,而不修边幅。各位了解箇中道理之后,可以自行添加修改,让程式码更美观。

显示大数

在萤幕上印出大数可以这么做。

 
  1. void  print int  100 ])
  2. {
  3.     int   =  100  -  ;             //要印的数字位置
  4.     while  ( ] ==  )  --;       //数字开头的零都不印
  5.     while  (  >=  )  cout  <<  --];
  6. }

如果这个大数有可能是零,就得加个几行程式码。

 
  1. void  print int  100 ])
  2. {
  3.     int   =  100  -  ;
  4.     while  (  >=   &&  ] ==  )  --;
  5.     
  6.     if  (  <  )
  7.         cout  <<  '0' ;
  8.     else
  9.         while  (  >=  )  cout  <<  --];
  10. }

比较大小

比较哪个数字比较大。

 
  1. // a > b
  2. bool  largerthan int  100 ],  int  100 ])
  3. {
  4.     for  ( int  100 ;  >= ;  --)    //从高位数开始比,对应的位数相比较。
  5.         if  ( ] !=  ])        //发现ab不一样大,马上回传结果。
  6.             return  ] >  ];
  7.     return  false ;        //完全相等
  8. }

加法运算

大数的四则运算不会很困难。这里提供大数加法的粗略程式码,希望能一目了然。

 
  1. // c = a + b;
  2. void  add int  100 ],  int  100 ],  int  c100 ])
  3. {
  4.     for  ( int  ;  100 ;  ++)    //对应的位数相加
  5.         ] =  ] +  ];
  6.         
  7.     for  ( int  ;  100 ;  ++)  //一口气进位
  8.     {
  9.         ] +=  ] /  10 ;     //进位
  10.         ] %=  10 ;              //进位后余下的数
  11.     }
  12. }

大数的运算有个有趣的地方,就是运算时不用立即进位,可以后来再去一口气进位。这件事情值得细想。

UVa 10035

减法运算

这里继续提供大数减法的粗略程式码。

 
  1. void  sub ​​( int  100 ],  int  100 ],  int  c100 ])
  2. {
  3.     for  ( int  ;  100 ;  ++)
  4.         ] =  ] -  ];
  5.         
  6.     for  ( int  ;  100 ;  ++)  //一口气借位和补位
  7.         if  ( ] <  )
  8.         {
  9.             ]--;            //借位
  10.             ] +=  10 ;          //补位
  11.         }
  12. }

乘法运算

大数乘法的粗略程式码。我一定要强调它是粗略的。

 
  1. void  mul int  100 ],  int  100 ],  int  c100 ])
  2. {
  3.     for  ( int  ;  100 ;  ++)
  4.         ] =  ;
  5.  
  6.     for  ( int  ;  100 ;  ++)
  7.         for  ( int  ;  100 ;  ++)
  8.             if  (  <  100 )
  9.                 ] +=  ] *  ];
  10.                 
  11.     for  ( int  ;  100 ;  ++)  //一口气进位
  12.     {
  13.         ] +=  ] /  10 ;
  14.         ] %=  10 ;
  15.     }
  16. }

至于大数乘以int 是比较容易的。

 
  1. void  mul int  100 ],  int  ,  int  100])
  2. {
  3.     for  ( int  ;  100 ;  ++)
  4.         ] =  ] *  ;
  5.                 
  6.     for  ( int  ;  100 ;  ++)  //一口气进位
  7.     {
  8.         ] +=  ] /  10 ;
  9.         ] %=  10 ;
  10.     }
  11. }

UVa 338 10106

除法运算

大数除法可直接使用长除法。还满复杂的。程式码就随便写写啰!

 
  1. void  div int  100 ],  int  100 ],  int  c100 ])
  2. {
  3.     int  100 ];
  4.     
  5.     for  ( int  100 ;  >= ;  --)
  6.         for  ( int  ;  ;  --)  //尝试商数
  7.         {
  8.             mul ,  ,  );
  9.             if  ( largerthan ,  ))
  10.             {
  11.                 sub ,  ,  );
  12.                 break ;
  13.             }
  14.         }
  15. }

商数范围是零到九,所以必须一一尝试。可以利用高位数相除来估计商数的范围,便不必一一尝试。这里不加说明。

至于大数除以int 是比较容易的。

 
  1. void  div int  100 ],  int  ,  int  100])
  2. {
  3.     int   =  ;
  4.     for  ( int  100 ;  >= ;  --)
  5.     {
  6.          =   *  10  +  ];
  7.         ] =   /  ;
  8.          %=  ;
  9.     }
  10. }

开平方根运算

大数开平方根可利用直式开方法。【待补文字】

UVa 10023

改进资料结构

一个栏位只存一个数字有点浪费。

int 的范围约为十位数字,一个栏位其实能够存入九个位数的。一个栏位可存九个位数,那么1000 格的阵列,便可从原来的1000 位数,摇身一变成为9000 位数;一个栏位可存九个位数,若要表示1000 位数,只需要112 格的阵列就可以了。这个新想法,相当的节省空间,运算次数也会随之减少。

不过,如果一个栏位存了很多位数,会对运算造成什么影响呢?

从最简单的加法、乘法开始思考好了:

首先,进位会受影响。如果一个栏位存了两位数字,那么做进位时,要每到100 才能进位。

第二,进位后会溢位( overflow )吗?进位会让隔壁的栏位增加一些数字。如果隔壁的栏位原本就有一个很大的数字,那么它加上进位的数值之后,会不会产生溢位?

第三,乘法是将某两个栏位相乘,加到另一个栏位上。两个栏位相乘,如果他们各是8 位数,相乘之后至少也有15 位数,这远超过int 的上限了,怎么可能存进一个int 之中呢?

或许还会有很多的问题需要考虑。

虽然问题重重,但是也并不代表一个栏位还是只能存一个数字吧?一个栏位存个两三位,应该不成问题吧?这些问题就留给大家思考,在此不加赘述。

UVa 288 10220 10814 10925 748

GMP

GMP 是一个C/C++ 大数运算函式库,相当实用,读者可上网搜寻之。