定義,高精度加法,高精度減法,單精度乘法,高精度乘法,高精度除法,高精度階乘,C++的優雅實現,實現,輸入輸出,大小比較,加法,減法,乘法,除法和取模,使用,最佳化與改進,
定義
高精度加法
高精度運算主要解決以下三個問題:
運算因子超出了整型、實型能表示的範圍,肯定不能直接用一個數的形式來表示。在
Pascal中,能表示多個數的數據類型有兩種:數組和字元串。
數組:每個數組元素存儲1位(在最佳化時,這裡是一個重點!),有多少位就需要多少個數組元素;用數組表示數的優點:每一位都是數的形式,可以直接加減;運算時非常方便。用數組表示數的缺點:數組不能直接輸入;輸入時每兩位數之間必須有
分隔設定,不符合數值的輸入習慣;
字元串:String型字元串的最大長度是255,可以表示255位。
Ansistring型字元串長度不受限制。用字元串表示數的優點:能直接輸入輸出,輸入時,每兩位數之間不必分隔設定,符合數值的輸入習慣;用字元串表示數的缺點:字元串中的每一位是一個字元,不能直接進行運算,必須先將它轉化為數值再進行運算;運算時非常不方便;
綜合以上所述,對上面兩種數據結構取長補短:用字元串讀入數據,用數組存儲數據:
var st:string;
x,y:
array[0..255]of integer;{定義兩個數組,X和Y,用來儲存數}
i,j,l1,l2:integer;
begin
readln(st);
l1:=length(st);{------length(x),該函式是獲取字元串X的長度,返回為整型}
for i:=0 to 255 do x[i]:=0;{數組初始化,該句等價於‘
fillchar(x,sizeof(x),o);’,即給一數組整體
賦值,但運行速度快於用‘for’語句對數組中的每一個數賦值}
for i:=l1 downto 1 do
x[l1-i+1]:=ord(st[i])-ord('0');{------這裡是重點,把字元串轉換為數值,儲存在數組中}
readln(st);
l2:=length(st);{------length(x),該函式是獲取字元串X的長度,返回為整型}
for i:=0 to 255 do y[i]:=0;{數組初始化,該句等價於‘
fillchar(y,sizeof(y),o);’}
for i:=l2 downto 1 do
y[l2-i+1]:=ord(st[i])-ord('0');{------這裡是重點,把字元串轉換為數值,儲存在數組中}
對字元串轉為數值原理補充:ord(x)-48,如果X='1',因為'1'的ASCLL碼是49,所以減去48就等於1,間接地把字元轉換為數值了,各位初手要好好體會.
二、運算過程
注意的問題:
(1)
運算順序:兩個數靠右對齊;從低位向高位運算;先計算低位再計算高位;
(2)運算規則:同一位的兩個數相加再加上從低位來的進位,成為該位的和;這個和去掉向高位的進位就成為該位的值;如上例:3+8+1=12,向前一位進1,本位的值是2;可藉助MOD、DIV運算完成這一步;
(3)最後一位的進位:如果完成兩個數的相加後,進位位值不為0,則應添加一位;
(4)如果兩個加數
位數不一樣多,則按位數多的一個進行計算;
if l1<l2 then l1:=l2;
for i:=1 to l1 do
begin
x[i]:=x[i]+y[i];
x[i+1]:=x[i+1]+x[i] div 10;
x[i]:=x[i] mod 10;
end;
三、結果的輸出(這也是最佳化的一個重點)
按運算結果的實際位數輸出
var st:string;
x,y:array[0..255]of integer;
i,j,l1,l2:integer;
begin
readln(st);
l1:=length(st);
for i:=0 to 255 do x[i]:=0;
for i:=l1 downto 1 do
x[l1-i+1]:=ord(st[i])-ord('0');
readln(st);
l2:=length(st);
for i:=0 to 255 do y[i]:=0;
for i:=l2 downto 1 do
y[l2-i+1]:=ord(st[i])-ord('0');
if l1<l2 then l1:=l2;
for i:=1 to l1 do
begin
x[i]:=x[i]+y[i];
x[i+1]:=x[i+1]+x[i] div 10;
x[i]:=x[i] mod 10;
end;
write('x+y=');
j:=255;
while x[j]=0 do j:=j-1;
for i:=j downto 1 do write(x[i]);
readln;
end.
四、最佳化:
以上的方法的有明顯的缺點:
(1)浪費空間:一個
整型變數(-32768~32767)只存放一位(0~9);
(2)浪費時間:一次加減只處理一位;
針對以上問題,我們做如下最佳化:一個
數組元素存放四位數;(integer的最大範圍是32767,5位的話可能導致出界)將標準數組改為緊縮數組。第一步的具體方法:
l:=length(s1);
k1:=260;
repeat {————有關字元串的知識}
s:=copy(s1,l-3,4);
val(s,a[k1],code);
k1:=k1-1;
s1:=copy(s1,1,l-4);
l:=l-4;
until l<=0;
k1:=k1+1;
而因為這個改進,算法要相應改變:
(1)運算時:不再逢十進位,而是逢萬進位(mod 10000; div 10000);
(2)輸出時:最高位直接輸出,其餘各位,要判斷是否足夠4位,不足部分要補0;例如:1,23,2345這樣三段的數,輸出時,應該是100232345而不是1232345。
改進後的算法:
var a,b:string; k,i,c,d:longint; e,z,y:array[0..255]of integer;
begin
readln(a);
readln(b);
if length(b)>length(a) then for i:=1 to length(b)-length(a) do
a:='0'+a
else for i:=1 to length(a)-length(b) do
b:='0'+b;
for i:=length(a) downto 1 do
begin
c:=ord(a[i])-48;
d:=ord(b[i])-48;
if c+d<10 then e[i]:=e[i]+c+d else begin e[i]:=e[i]+c+d-10;e[i-1]:=1; end;
end;
if e[0]=1 then k:=0 else k:=1;
for i:=k to length(a) do
write(e[i]);
end.
C++參考程式:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int main()
{
char a1[100],b1[100];
int a[100],b[100],c[100],lena,
lenb,lenc,i,x;
memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); memset(c,0,sizeof(c)); gets(a1); gets(b1); //輸入加數與
被加數 lena=
strlen(a1); lenb=strlen(b1); for (i=0;i<=lena-1;i++) a[lena-i]=a1[i]-48; //加數放入a數組 for (i=0;i<=lenb-1;i++) b[lenb-i]=b1[i]-48; //加數放入b數組 lenc =1; x=0; while (lenc <=lena||lenc <=lenb) { c[lenc]=a[lenc]+b[lenc]+x; //兩數相加 x=c[lenc]/10; c[lenc]%=10; lenc++; } c[lenc]=x; if (c[lenc]==0) lenc--; //處理最高進位 for (i=lenc;i>=1;i--) cout<<c[i]; //輸出結果 cout<<endl;
return 0; }
高精度減法
和
高精度加法相比,減法在差為負數時處理的細節更多一點:當
被減數小於減數時,差為負數,差的絕對值是減數減去被減數;在程式實現上用一個變數來存儲符號位,用另一個數組存差的絕對值。
算法流程:(1).讀入被減數S1,S2(字元串);
(2).置符號位:判斷被減數是否大於減數:大則將符號位置為空;小則將符號位置為“- ”,交換減數與被減數;
(3).被減數與減數處理成數值,放在數組中;
(4).運算:A、取數;
C、減,將運算結果放到差數組相應位中;
D、判斷是否運算完成:是,轉5;不是,轉A;
(5).列印結果:符號位,第1位,循環處理第2到最後一位;
如果位數一樣,直接比較字元串大小;否則,位數多的大。
k1:=length(s1); k2:=length(s2);
if k1=k2 then
if s1<s2 then begin fh:='-'; s:=s1;s1:=s2; s2:=s;end
else if k1<k2 then begin fh:='-';s:=s1;s1:=s2;s2:=s;end;{s1存被減數,fh存符號}
▲將字元串處理成數值:
l:=length(s1);{求出s1的長度,也即s1的位數;有關字元串的知識。}
k1:=260;
for i:=l downto 1 do
begin
a[k1]:=ord(s1[i])-48;{將字元轉成數值}
k1:=k1-1;
end;
k1:=k1+1;
▲運算(減法跟
加法比較,減法退位處理跟加法進位處理不一樣):
處理退位: 跟加法一樣,在for語句外面先將退位清零,用
被減數再減去退位,{注意:由於每一個
數位不一定都得向前一位
借位,所以這裡退位得清零。例如,234-25,個位需借位,而十位不用} 接著,再判斷,當被減數某一位不夠減時,則需加上前一位退位過來的數。注意:由於這裡採用最佳化方法,所以退一位,就等於後一位加上10000。)最後,再拿一個數組來存儲兩個
減數的差。
jw:=0;
for i:=260 downto k1 do
begin
a[i]:=a[i]-jw;{此處jw為從剛處理的那一位上從本一位上的借位}
jw:=0; {此處jw為I 位準備向高一位的借位}
if a[i]<b[i] then
begin
jw:=1;
a[i]:=a[i]+10000;
end;
c[i]:=a[i]-b[i]
end;
▲列印結果: 先找到差的第一個非零數,如果差的所有位數都為零,就直接輸出零; 如果不是,就輸出符號位和差的第一位。剩下部分,列印補足零;因為最佳化後的
高精度減法,是把每四個數位分成一段,而每一段則必須有四個數,當有一段不足四個數時,就得用"0"補足.(如:第一位是'1',第二位是'34',第三位是'345',第四位是'8', 則應寫為'').注意:第一位不用補零,(如:第一位為'3',則寫成'3').
while (c[k]=0) and (k<=260) do k:=k+1;
if k>260 then write('0')
else begin
write(fh,c[k]);{k是差的第1位;}
for i:=k+1 to 260 do
begin
if c[i]<100 then write('0');
if c[i]<10 then write('0');
write(c[i]);
end;
end;
參考程式:
program ZDloveQC;
var s1,s2,s3,s4,s:string;
a,b,c:array[1..260]of integer;
i,k1,k2,l,code,jw:longint;
fh:string;
begin
readln(s1); readln(s2);
k1:=length(s1); k2:=length(s2); fh:='';
if k1=k2 then
if s1<s2 then begin fh:='-';s:=s1; s1:=s2; s2:=s; end;
if k1<k2 then begin fh:='-';s:=s1; s1:=s2; s2:=s; end;
k1:=260;
l:=length(s1);
repeat
s3:=copy(s1,l-3,4);
val(s3,a[k1],code);
dec(k1);
s1:=copy(s1,1,l-4);
l:=l-4;
until l<=0;
inc(k1);
l:=length(s2);
k2:=260;
repeat
s4:=copy(s2,l-3,4);
val(s4,b[k2],code);
dec(k2);
s2:=copy(s2,1,l-4);
l:=l-4;
until l<=0;
inc(k2);
jw:=0;
for i:=260 downto k1 do
begin
a[i]:=a[i]-jw;
jw:=0;
if a[i]<b[i] then
begin
jw:=1;
a[i]:=a[i]+10000;
end;
c[i]:=a[i]-b[i];
end;
while (c[k1]=0)and(k1<260) do inc(k1);
if k1>260 then writeln('0')
else begin
write(fh,c[k1]);
for i:=k1+1 to 260 do
begin
if c[i]<1000 then write('0');
if c[i]<100 then write('0');
if c[i]<10 then write('0');
write(c[i]);
end;
end;
end.
C++參考程式:
#include<stdio.h>
#include<ctype.h>
#include<string.h>
#include<stdlib.h>
#include<stdbool.h>
int const n=1000;
typedef int arr[n];
int c[2*n+1]={}, i,j,k; arr a,b;void dushu(arr &s){int i=0,j,k,m;<br/> char b[400],ch;<br/> scanf("%c",&ch);<br/> while ((ch>=48)&& (ch <=57) )<br/> { i=i+1;<br/> b[i]=ch;<br/> scanf("%c",&ch);<br/> } k=0; for(j=i; j>=1; j=j-1) { k=k+1; s[k]=b[j]-48; } }void shuchu(int c[n]) { int i=n; j=0; while ((c[i]==0) && (i>1)) i--; for (j=i; j>0; j--) printf("%d",c[j]); printf("\n"); } bool compare(arr &a,arr &b){ int i,j,k; for (i=n; i>0; i--) { if (a[i]>b[i]) {return true;} else if (a[i]<b[i]) {return false;}; } } void change(arr &a, arr &b) { int i,t; for(i=1; i<=n; i++) { t=a[i]; a[i]=b[i]; b[i]=t; } } void jiafa(arr a,arr b) {int i,j,k,m,s,t=0;<br/> for (i=1; i<=n+1; i++)<br/> {<br/> s=a[i]+b[i]+t; <br/> c[i]=s % 10;<br/> t=s / 10;<br/> } shuchu(c); } void jianfa(arr &a, arr &b) { int i,j,k,s; memset(c,0,2*n+1); for (i=1; i<=n; i++) { if (a[i]>=b[i]) c[i]=a[i]-b[i]; else { c[i]=a[i]-b[i]+10; a[i+1]--; } } shuchu(c); }void chengfa(arr a, arr b) { int i,j,k,m; memset(c,0,2*n+1); for (i=1; i<=n; i++) for (j=1; j<=n; j++) { m=i+j-1; c[m]=a[j]*b[i]+c[m]; c[m+1]=c[m] / 10; c[m]=c[m]% 10; } shuchu(c); } main(){ dushu(a); dushu(b); jiafa(a,b); if (!compare(a,b)) { printf("%c",'-'); change(a,b); } jianfa(a,b); chengfa(a,b); system("pause"); return 0;}
單精度乘法
單精度乘法過程樣例:
const
maxcount=進制位
maxlen=記錄高精度數組大小
procedure mulnum(a:bignum;x:longint;,var c:bignum);
var
i:longint;
begin
fillchar(c,sizeof(c),0);c[0]:=a[0];
for i:=1 to c[0] do c[i]:=a[i]*x;
for i:=1 to c[0] do {進位}
begin
inc(c[i+1],c[i] div maxcount);
c[i]:=c[i] mod 10;
end;
while c[c[0]+1]>0 do
begin
inc(c[0]);
inc(c[c[0]+1],c[c[0]] div maxcount);
c[c[0]]:=c[c[0]] mod maxcount;
end;
end;
高精度乘法
②把s1、s2分成4位一段,轉成數值存在數組a,b中;記下a,b的長度k1,k2;
③i賦為b中的最低位;
④從b中取出第i位與a相乘,累加到另一數組c中;(注意:累加時錯開的位數應是多少位?)
⑤i:=i-1;檢測i值:小於k2則轉⑥,否則轉④
⑥列印結果
參考程式:
program chengfa;
const n=100;
type ar=array [1..n] of integer;
var a,b:ar; k1,k2,k:integer;
c:array [1..200] of integer;
s1,s2:string;
procedure fenge(s:string;var d:ar; var kk:integer); {將s分割成四位一組存放在d中,返回的kk值指向d的最高位}
var ss:string;
i,code:integer;
begin
i:=length(s);
kk:=n;
repeat
ss:=copy(s,i-3,4);
val(ss,d[kk],code);
kk:=kk-1;
s:=copy(s,1,i-4);
i:=i-4;
until i<0;
kk:=kk+1;
end;
procedure init;
var i:integer;
begin
for i:=1 to n do begin a:=0; b:=0; end;
for i:=1 to 2*n do c:=0;
write('input 2 numbers:');
readln(s1);
readln(s2);
fenge(s1,a,k1);
fenge(s2,b,k2);
end;
procedure jisuan;
var i,j,m:integer; x,y,z,jw:longint;
begin
i:=n; k:=2*n;
repeat
x:=b; z:=0; m:=k; jw:=0;
for j:=n downto k1 do
begin
y:=a[j];
z:=c[m];
x:=x*y+z+jw;
jw:=x div 10000;
c[m]:=x mod 10000;
m:=m-1;
x:=b;
end;
if jw<>0 then c[m]:=jw else m:=m+1;
i:=i-1;
k:=k-1;
until i<k2;
k:=m;
end;
procedure daying;
var i:integer;
begin
write(c[k]);
for i:=k+1 to 2*n do
begin
if c<1000 then write('0');
if c<100 then write('0');
if c<10 then write('0');
write(c);
end;
writeln;
end;
begin
init;
jisuan;
daying;
end.
教材“基礎編”P87高精乘法參考程式:
program ex3_1;
var
a,b,c:array[0..1000] of word;
procedure init;
var
s:string;
ok,i,j:integer;
begin
readln(s);
a[0]:=length(s);
for i:=1 to a[0] do
val(s[a[0]-i+1],a,ok);
readln(s);
b[0]:=length(s);
b[0]:=length(s);
for i:=1 to b[0] do
val(s[b[0]-i+1],b,ok);
end;
procedure highmul;
var i,j,k:integer;
begin
c[0]:=a[0]+b[0];
for i:=1 to b[0] do
for j:=1 to a[0]+1 do
begin
inc(c[i+j-1],a[j]*b mod 10);
c[i+j]:=c[i+j]+(a[j]*b div 10)+(c[i+j-1] div 10);
c[i+j-1]:=c[i+j-1] mod 10;
end;
end;
procedure print;
var i:integer;
begin
while c[c[0]]=0 do dec(c[0]);
for i:=c[0] downto 1 do
write(c);
end;
begin
init;
highmul;
print;
end.
C++參考程式:
#include<iostream> #include<cstring> #include<cstdio> using namespace std; int main() { char a1[100],b1[100]; int a[100],b[100],c[100],lena,lenb,lenc,i,j,x; memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); memset(c,0,sizeof(c)); gets(a1);gets(b1); lena=strlen(a1);lenb=strlen(b1); for (i=0;i<=lena-1;i++) a[lena-i]=a1[i]-48; for (i=0;i<=lenb-1;i++) b[lenb-i]=b1[i]-48; for (i=1;i<=lena;i++) { x=0;//用於存放進位 for (j=1;j<=lenb;j++) //對乘數的每一位進行處理 { c[i+j-1]=a[i]*b[j]+x+c[i+j-1]; //當前乘積+上次乘積進位+原數x=c[i+j-1]/10; c[i+j-1] %= 10; } c[i+lenb]=x;//進位 } lenc=lena+lenb; while (c[lenc]==0&&lenc>1) //刪除前導0 lenc--; for (i=lenc;i>=1;i--) cout<<c[i]; cout<<endl; return 0; }
高精度除法
#include<iostream>#include<fstream>#include<cmath>#include<cstdio>#include<algorithm>using namespace std;const long long ans_ary=500;//修改ans_ary的值以改變輸出數據精度long long ans[ans_ary]={0};void get_shang(int k){//高精度累加 for(long i=0;i<=ans_ary;i++){ int jinwei=int(ans[i]/100000); ans[i]*=10; ans[i]=(ans[i]-jinwei*1000000); ans[i+1]+=jinwei; } ans[0]+=k; return;}void print(){//輸出 for(int i=0;i<ans_ary;i++){ int jinwei=0; jinwei=int(ans[i]/100000); ans[i+1]+=jinwei; ans[i]-=100000*jinwei; } ans[0]/10; long long cnt=0; for(int i=ans_ary-1;i>=0;i--){ cout<<"line"<<' '<<cnt+1<<' '<<"count"<<' '<<cnt*5+1<<' '; cnt++; if(ans[i]==0)cout<<"00000"; else if(ans[i]<10)cout<<"0000"<<ans[i]; else if(ans[i]<100)cout<<"000"<<ans[i]; else if(ans[i]<1000)cout<<"00"<<ans[i]; else if(ans[i]<10000)cout<<'0'<<ans[i]; else if(ans[i]>=10000)cout<<ans[i]; cout<<endl; }}int main(){//主函式 freopen("output.out","w",stdout);//會在編譯完成的應用程式所在位置生成一個“output.out”的檔案 long long n,m; cin>>m>>n;//被除數,除數(m/n) long long ready=m; for(long long i=0;i<=ans_ary*5;i++){ long long rest=0; long long shang=int(ready/n); get_shang(shang); ready=ready-shang*n; ready=ready*10; } print(); system("pause"); return 0;}
程式如下:
program HighPrecision3_Multiply1;
const
fn_inp='hp5.inp';
fn_out='hp5.out';
maxlen=100; { max length of the number }
type
hp=record
len:integer; { length of the number }
s:array[1..maxlen] of integer
{ s[1] is the lowest position
s[len] is the highest position }
end;
var
x,y:hp;
z,w:integer;
procedure PrintHP(const p:hp);
var i:integer;
begin
for i:=p.len downto 1 do write(p.s[i]);
end;
procedure init;
var
st:string;
i:integer;
begin
assign(input,fn_inp);
reset(input);
readln(st);
x.len:=length(st);
for i:=1 to x.len do { change string to HP }
x.s:=ord(st[x.len+1-i])-ord('0');
close(input);
end;
procedure Divide(a:hp;b:integer;var c:hp;var d:integer);
{ c:=a div b ; d:=a mod b }
var i,len:integer;
begin
fillchar(c,sizeof(c),0);
len:=a.len;
d:=0;
for i:=len downto 1 do { from high to low }
begin
d:=d*10+a.s[i];
c.s:=d div b;
d:=d mod b;
end;
while(len>1) and (c.s[len]=0) do dec(len);
c.len:=len;
end;
procedure main;
begin
Divide(x,z,y,w);
end;
procedure out_;
begin
assign(output,fn_out);
rewrite(output);
PrintHP(y);
writeln(w);
close(output);
end;
begin
init;
main;
out_;
end.
2).高精度除以高精度
程式如下:
版本一:
programHighPrecision4;{outputalpha/beta}const fn_inp='hp6.inp'; fn_out='hp6.out'; maxlen=100;{maxlengthofthenumber}type hp=record len:integer;{lengthofthenumber} s:array[1..maxlen]ofinteger {s[1]isthelowestposition s[len]isthehighestposition} end;var x:array[1..2]ofhp; y,w:hp;{x:input;y:output}procedurePrintHP(constp:hp);vari:integer;begin fori:=p.lendownto1dowrite(p.s[i]);end;procedureinit;var st:string; j,i:integer;begin //assign(input,fn_inp); //reset(input); forj:=1to2do begin readln(st); x[j].len:=length(st); fori:=1tox[j].lendo{changestringtoHP} x[j].s[i]:=ord(st[x[j].len+1-i])-ord('0'); end; //close(input);end;procedureSubtract(a,b:hp;varc:hp);{c:=a-b,supposea>=b}vari,len:integer;begin fillchar(c,sizeof(c),0); ifa.len>b.lenthenlen:=a.len{getthebiggerlengthofa,b} elselen:=b.len; fori:=1tolendo{subtractfromlowtohigh} begin inc(c.s[i],a.s[i]-b.s[i]); ifc.s[i]<0then begin inc(c.s[i],10); dec(c.s[i+1]);{add1toahigherposition} end; end; while(len>1)and(c.s[len]=0)dodec(len); c.len:=len;end;functionCompare(consta,b:hp):integer;{1ifa>b0ifa=b-1ifa<b}varlen:integer;begin ifa.len>b.lenthenlen:=a.len{getthebiggerlengthofa,b} elselen:=b.len; while(len>0)and(a.s[len]=b.s[len])dodec(len); {findapositionwhichhaveadifferentdigit} iflen=0thencompare:=0{nodifference} elsecompare:=a.s[len]-b.s[len];end;procedureMultiply10(vara:hp);{a:=a*10}vari:Integer;begin fori:=a.lendownto1doa.s[i+1]:=a.s[i]; a.s[1]:=0; inc(a.len); while(a.len>1)and(a.s[a.len]=0)dodec(a.len);end;procedureDivide(a,b:hp;varc,d:hp);{c:=adivb;d:=amodb}vari,j,len:integer;begin fillchar(c,sizeof(c),0); len:=a.len; fillchar(d,sizeof(d),0); d.len:=1; fori:=lendownto1do begin Multiply10(d); d.s[1]:=a.s[i];{d:=d*10+a.s} {c.s:=ddivb;d:=dmodb;} {while(d>=b)dobegind:=d-b;inc(c.s)end} while(compare(d,b)>=0)do begin Subtract(d,b,d); inc(c.s[i]); end; end; while(len>1)and(c.s[len]=0)dodec(len); c.len:=len;end;proceduremain;begin Divide(x[1],x[2],y,w);end;procedureout;begin //assign(output,fn_out); //rewrite(output); PrintHP(y);{outputalphadivbeta} writeln; PrintHP(w);{outputalphamodbeta} writeln; //close(output);end;begin init; main; out;end.
版本二:
programaaa;typebig=array[0..500]ofinteger;vara,b,c,d:big;proceduremake;//讀入數據vars:ansistring;i:longint;beginreadln(s);a[0]:=pos('',s)-1;fori:=1toa[0]doa[i]:=ord(s[a[0]-i+1])-ord('0');delete(s,1,a[0]+1);b[0]:=length(s);fori:=1tob[0]dob[i]:=ord(s[b[0]-i+1])-ord('0');end;proceduremulti10(varc:big);//餘數乘十vari:longint;begininc(c[0]);fori:=c[0]downto2doc[i]:=c[i-1];ifc[c[0]]=0thendec(c[0]);end;functioncompare(vara,b:big):integer;//比較vari:longint;beginifa[0]>b[0]thenexit(1);ifa[0]<b[0]thenexit(-1);fori:=a[0]downto1doifa[i]>b[i]thenexit(1)elseifa[i]<b[i]thenexit(-1);exit(0);end;procedureminus(vara,b:big);//減法vari:longint;beginfori:=1toa[0]dobegina[i]:=a[i]-b[i];ifa[i]<0thenbegina[i]:=a[i]+10;a[i+1]:=a[i+1]-1;end;end;while(a[a[0]]=0)and(a[0]>1)dodec(a[0]);end;proceduredivision(vara,b,c,d:big);//除法,c為商,d為餘數。vari:longint;beginfillchar(c,sizeof(c),0);fillchar(d,sizeof(d),0);d[0]:=1;fori:=a[0]downto1dobeginmulti10(d);d[1]:=a[i];whilecompare(d,b)>-1dobeginminus(d,b);inc(c[i]);end;end;c[0]:=a[0];while(c[c[0]]=0)and(c[0]>1)dodec(c[0]);end;procedureprint(vara:big);vari:longint;beginfori:=a[0]downto1dowrite(a[i]);writeln;end;beginmake;division(a,b,c,d);print(c);{print(d);}readln;readln;end.
高精度階乘
作為一種
高精度乘法的擴展算法,實質為高精度乘低精度,算法如下: var
a:
array[1..10000] of longint;
i,j,k,l,p,o,q,x,y,w:integer;
begin
read(i);
a[1]:=1;
w:=1;
for j:=1 to i do
begin
y:=0; //到“For”前可省,但改為for k:=1 to 10000 do
x:=j;
while x>0 do
begin
y:=y+1;
x:=x div 10;
end;
o:=0;
for k:=w to l+y+1 do
begin
q:=a[k]*j+o;
o:=q div 10;
a[k]:=q mod 10;
end;
l:=10000;
while (a[l]=0) and (l>1) do l:=l-1;
w:=1;
while (a[w]=0) and (w<9999) do w:=w+1;
end;
for p:=l downto 1 do
write(a[p]);
writeln;
end.
C++的優雅實現
我們知道,C++是一個
面向對象的語言。上述所有代碼的實現都是
面向過程的,都是以高精度運算為主體進行編程。然而,在實際套用中,高精度通常只作為程式的一部分而出現,在這樣的情況下,上述代碼難以直接移植、使用的特性暴露無遺。我們用C++的
面向對象編程特性來做一次非常好用的高精度。
實現
我們使用標準庫vector做基類,完美解決位數問題,同時更易於實現。
高精度類型Wint包含一個低精度轉高精度的初始化函式,可以自動被編譯器調用,因此無需單獨寫高精度數和低精度數的運算函式,十分方便;還包含了一個在各類運算中經常用到的進位小函式。
#include<iostream>#include<vector>#include<string>using namespace std;struct Wint:vector<int>{ Wint(int n=0)//默認初始化為0,但0的保存形式為空 { push_back(n); check(); } Wint& check() { while(!empty()&&!back())pop_back();//去除最高位可能存在的0 if(empty())return *this; for(int i=1; i<size(); ++i) { (*this)[i]+=(*this)[i-1]/10; (*this)[i-1]%=10; } while(back()>=10) { push_back(back()/10); (*this)[size()-2]%=10; } return *this;//為使用方便,將進位後的自身返回引用 }};
輸入輸出
平淡無奇,有很多讀入的方法。這裡偷個懶,直接讀入一個字元串再轉入Wint中。
istream& operator>>(istream &is,Wint &n){ string s; is>>s; n.clear(); for(int i=s.size()-1; i>=0; --i)n.push_back(s[i]-'0'); return is;}ostream& operator<<(ostream &os,const Wint &n){ if(n.empty())os<<0; for(int i=n.size()-1; i>=0; --i)os<<n[i]; return os;}
大小比較
比較,只需要寫兩個,其他的直接代入即可。值得注意的是,這裡用常量引用當參數,避免拷貝更高效。
bool operator!=(const Wint &a,const Wint &b){ if(a.size()!=b.size())return 1; for(int i=a.size()-1; i>=0; --i) if(a[i]!=b[i])return 1; return 0;}bool operator==(const Wint &a,const Wint &b){ return !(a!=b);}bool operator<(const Wint &a,const Wint &b){ if(a.size()!=b.size())return a.size()<b.size(); for(int i=a.size()-1; i>=0; --i) if(a[i]!=b[i])return a[i]<b[i]; return 0;}bool operator>(const Wint &a,const Wint &b){ return b<a;}bool operator<=(const Wint &a,const Wint &b){ return !(a>b);}bool operator>=(const Wint &a,const Wint &b){ return !(a<b);}
加法
加法,先實現+=,這樣更簡潔高效。注意各個參數有別。
Wint& operator+=(Wint &a,const Wint &b){ if(a.size()<b.size())a.resize(b.size()); for(int i=0; i!=b.size(); ++i)a[i]+=b[i]; return a.check();}Wint operator+(Wint a,const Wint &b){ return a+=b;}
減法
減法,返回差的絕對值,由於後面有交換,故參數不用引用。
Wint& operator-=(Wint &a,Wint b){ if(a<b)swap(a,b); for(int i=0; i!=b.size(); a[i]-=b[i],++i) if(a[i]<b[i])//需要借位 { int j=i+1; while(!a[j])++j; while(j>i) { --a[j]; a[--j]+=10; } } return a.check();}Wint operator-(Wint a,const Wint &b){ return a-=b;}
乘法
乘法不能先實現*=,原因自己想。
Wint operator*(const Wint &a,const Wint &b){ Wint n; n.assign(a.size()+b.size()-1,0); for(int i=0; i!=a.size(); ++i) for(int j=0; j!=b.size(); ++j) n[i+j]+=a[i]*b[j]; return n.check();}Wint& operator*=(Wint &a,const Wint &b){ return a=a*b;}
除法和取模
除法和取模先實現一個帶餘除法函式。當然,高精度除法也可以用二分檔案法實現,不過效率過低且代碼冗長,這裡使用常規豎式除法。
Wint divmod(Wint &a,const Wint &b){ Wint ans; for(int t=a.size()-b.size(); a>=b; --t) { Wint d; d.assign(t+1,0); d.back()=1; Wint c=b*d; while(a>=c) { a-=c; ans+=d; } } return ans;}Wint operator/(Wint a,const Wint &b){ return divmod(a,b);}Wint& operator/=(Wint &a,const Wint &b){ return a=a/b;}Wint& operator%=(Wint &a,const Wint &b){ divmod(a,b); return a;}Wint operator%(Wint a,const Wint &b){ return a%=b;}
使用
通過重載運算符,還可以實現++、--、^、!、邏輯運算符等很多運算,十分簡單,此處都不寫了。
此時你幾乎可以像int一般便捷地使用Wint,甚至可以把Wint和int混合使用。
順手實現一個快速冪,可以看到和普通快速冪幾乎無異。
Wint pow(const Wint &n,const Wint &k){ if(k.empty())return 1; if(k==2)return n*n; if(k.back()%2)return n*pow(n,k-1); return pow(pow(n,k/2),2);}int main(){ Wint a,b; //可以把a或b改成int型,仍能正常使用 cin>>a>>b; cout<<(a<b)<<endl <<(a==b)<<endl <<a+b<<endl <<a-b<<endl <<a*b<<endl <<a/b<<endl <<a%b<<endl <<pow(a,b);}
最佳化與改進
上述高精度代碼已經能滿足正常使用需求了,不過仍然有最佳化和改進的空間:
一、萬進制最佳化
用int保存個位數顯然太過浪費,short型運算效率又沒有int型高(絕大部分機器對int有特別最佳化),在這樣的情況下,我們將改進後的Wint每位保存十進制下的四位(首位可能有前導0)即萬進制。在這樣的最佳化下,空間占用四分之一,加法快4倍,乘法16倍,而除法可達64倍之多。當然,這仍屬於常數級最佳化,不過底層運算十分頻繁的情況下還是值得考慮的。上述代碼無需作出太大調整,只需輸入輸出、進位、減法除法等函式略加改進即可,代碼略。
二、低精度最佳化
前面說過,目前高精度和低精度的運算會先將低精度提升到高精度再進行運算,這就有了最佳化空間。注意,這裡的最佳化低精度是基於Wint是由int組成這一特點進行的,大於int型的別的類型(如long long)仍需提升到Wint再運算。考慮到低精度數位數在十位以內,最佳化後效率提升其實不到十倍。不過,在高精度和低精度混合運算十分頻繁的情況下,專門寫最佳化的高精度和低精度的運算也聊勝於無。這裡先給出加法的最佳化示例。
Wint& operator+=(Wint &a,const int &b){ if(a.empty())a.push_back(b); else a.front()+=b; return a.check();}Wint operator+(Wint a,const int &b){ return a+=b;}Wint operator+(const int &b,Wint a)//注意重載不同順序的運算符{ return a+=b;}
三、初始化函式改進:
注意到初始化函式Wint(int n)會將大於int表示範圍的類型數如(unsigned long long)先轉為低精度再初始化,我們再增加(或者直接替換原先的)初始化函式,改為:
Wint(unsigned long long n=0)//需寫在Wint定義內{ while(n) { push_back(n%10); n/=10; }}
此外,在代碼中如果想定義到一個高精度常量(20位向上),就必須增加一個字元串初始化函式而不能直接賦一個整型常數(想想為什麼?)。但是,這個字元串初始化函式我們希望它不會像int型一樣在運算中自動提升至Wint。否則,像s+7這樣的表達式就會有意義(先將s隱形轉換至Wint再進行加法運算,返回一個Wint型結果),但通常不符合我們的預期而僅僅只是代碼錯誤,而編譯時無法找出,會給我們調試代碼帶來很大麻煩。所以,這個字元串初始化函式前需加關鍵字
explicit,來指出我們不希望隱式轉換的發生。
explicit Wint(const string &s)//需寫在Wint定義內{ for(int i=s.size()-1;i>=0;--i)push_back(s[i]-'0');//未判斷字元串合法情況}//現在你可以直接用字元串初始化Wint了,改進後輸入函式還能這樣寫istream& operator>>(istream &is,Wint &n){ string s; is>>s; n=Wint(s); return is;}