解法3-陣列與二進位混用大數運算
將大數看作一個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
//作者:afanty@vip.sina.com
//版本: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
//作者:afanty@vip.sina.com
//版本: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=CE,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[ki];
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進制來表示大數的,
所以其反彙編後大數的值在內存中表現出來有些“奇怪”。
好了,俺的大數運算庫源代碼將附在本貼之後(點擊下載),在VC中可以
直接編譯通過,如果大家在使用中發現有Bug,希望能夠通
知俺及時改正,謝謝!
解法2(網友提供)
http://www.wretch.cc/blog/tomas0011/12889212
A/B=C 餘 D
C=""
若A>=B
找到一數 n
B的尾數要乘 n 才能得到A的尾數,
所以
A=A-(B*n)
C=n & C
此時A的尾數必為0
將A的尾數去除(少一位)
重複以上步驟,則可以找到C,直到A<B,
則D=最後的A
解法1(網友提供的)
/*
http://www.wretch.cc/blog/taichunmin/12666528
它的使用方法呢
就是隨便找一個C++的編譯程式(例如Dev-C++)
編譯完成之後
在出來的程式框框內
直接打進兩個你要除的數就行了
(打完一個數字就要按 Enter 喔)
*/
#include<iostream>
#define LENGTH 10000
//你可以改變上面的數字來修改程式處理的位數
//數字越小程式越快
using namespace std;
int main()
{
char ca[LENGTH],cb[LENGTH],cc[LENGTH];
int la,lb,lc;
while(cin>>ca>>cb)
{
la=strlen(ca);
lb=strlen(cb);
bool bb=false;
for(int i=0;i<lb;i++)
if(cb[i]!='0')bb=true;
if(!bb)
{
cout<<"錯誤的輸入 除數不可為零"<<endl;
continue;
}
lc=0;
cout<<ca<<" ÷ "<<cb<<" = ";
for(int i=0;i<la;i++)ca[i]-='0';
for(int i=0;i<lb;i++)cb[i]-='0';
for(int i=0;i<LENGTH;i++)cc[i]=0;
for(int i=lb-1;i<la;i++)
{
bool ba=true;
while(ba)
{
if(!(i-lb>-1 && ca[i-lb]!=0))
for(int j=0;j<lb;j++)
if(ca[i-lb+1+j]!=cb[j])
{
if(ca[i-lb+1+j]<cb[j])
ba=false;
break;
}
if(ba)
{
for(int j=lb-1;j>-1;j--)
{
ca[i-lb+1+j]-=cb[j];
if(ca[i-lb+1+j]<0)
{
ca[i-lb+j]--;
ca[i-lb+1+j]+=10;
}
}
cc[lc]++;
}
}
lc++;
}
char *pa,*pc;
pa=&ca[0],pc=&cc[0];
if(lc==0)lc=1;
while(*pa==0 && pa<(&ca[la-1]))pa++;
while(*pc==0 && pc<(&cc[lc-1]))pc++;
for(int i=0;i<la;i++)ca[i]+='0';
for(int i=0;i<lc;i++)cc[i]+='0';
cout<<pc<<" ... "<<pa<<endl;
}
}