必威电竞|足球世界杯竞猜平台

高精度計算
來源:互聯網

高精度運算,是指參與運算的數(加數,減數,因子……)范圍大大超出了標準數據類型(整型,實型)能表示的范圍的運算。例如,求兩個20000位的數的和。這時,就要用到高精度算法了。

定義

高精度加法

高精度運算主要解決以下三個問題:

一、加數、減數、運算結果的輸入和存儲

運算因子超出了整型、實型能表示的范圍,肯定不能直接用一個數的形式來表示。在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,間接地把字符轉換為數值了,各位初手要好好體會.

二、運算過程

在往下看之前,大家先列豎式計算35+86。

注意的問題:

(1)運算順序:兩個數靠右對齊;從低位向高位運算;先計算低位再計算高位;

(2)運算規則:同一位的兩個數相加再加上從低位來的進位,成為該位的和;這個和去掉向高位的進位就成為該位的值;如上例:3+8+1=12,向前一位進1,本位的值是2;可借助MOD、DIV運算完成這一步;

(3)最后一位的進位:如果完成兩個數的相加后,進位位值不為0,則應添加一位;

(4)如果兩個加數位數不一樣多,則按位數多的一個進行計算;

if l1

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

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=1 then k:=0 else k:=1;

for i:=k to length(a) do

write(e[i]);

end.

C++參考程序:

#include

#include

#include

using namespace std;

int main()

{

char a1,b1;

int a,b,c,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[len阿倫·艾弗森]=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<

return 0; }

高精度減法

和高精度加法相比,減法在差為負數時處理的細節更多一點:當被減數小于減數時,差為負數,差的絕對值是減數減去被減數;在程序實現上用一個變量來存儲符號位,用另一個數組存差的絕對值。

算法流程:(1).讀入被減數S1,S2(字符串);

(2).置符號位:判斷被減數是否大于減數:大則將符號位置為空;小則將符號位置為“- ”,交換減數與被減數;

(3).被減數與減數處理成數值,放在數組中;

(4).運算:A、取數;

B、判斷是否需要借位;

C、減,將運算結果放到差數組相應位中;

D、判斷是否運算完成:是,轉5;不是,轉A;

(5).打印結果:符號位,第1位,循環處理第2到最后一位;

細節:▲如何判斷被減數與減數的大小?

如果位數一樣,直接比較字符串大小;否則,位數多的大。

k1:=length(s1); k2:=length(s2);

if k1=k2 then

if s1

else if k1

▲將字符串處理成數值:

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]

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

if k1

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);

迪吉多(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]

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

#include

#include

#include

#include

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;
char b,ch;
scanf("%c",&ch);
while ((ch>=48)&& (ch <=57) )
{ i=i+1;
b[i]=ch;
scanf("%c",&ch);
} 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] for (i=1; i<=n+1; i++)
{
s=a[i]+b[i]+t;
c[i]=s % 10;
t=s / 10;
} 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:=a;

for i:=1 to c do c[i]:=a[i]*x;

for i:=1 to c do {進位}

begin

inc(c[i+1],c[i] div maxcount);

c[i]:=c[i] MOD 10;

end;

while c[c+1]>0 do

begin

inc(c);

inc(c[c+1],c[c] div maxcount);

c[c]:=c[c] mod maxcount;

end;

end;

高精度乘法

高精度乘法基本思想和加法一樣。其基本流程如下:

①讀入被乘數s1,乘數s2

②把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

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:=length(s);

for i:=1 to a do

VAL(s[a-i+1],a,ok);

readln(s);

b:=length(s);

b:=length(s);

for i:=1 to b do

val(s[b-i+1],b,ok);

end;

procedure highmul;

var i,j,k:integer;

begin

c:=a+b;

for i:=1 to b do

for j:=1 to a+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 do dec(c);

for i:=c downto 1 do

write(c);

writeln;

end;

begin

init;

highmul;

print;

end.

C++參考程序:

#include #include #include using namespace std; int main() { char a1,b1; int a,b,c,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<

高精度除法

高精度除法:

1).高精度除以整型數據(integer);

程序如下:

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 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 }

XS:=ord(st[x.len+1-i])-ord('0');

readln(z);

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+AS[i];

計算機科學:=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;

writeln(w);

close(output);

end;

begin

init;

main;

out_;

end.

2).高精度除以高精度

程序如下:

版本一:

版本二:

高精度階乘

作為一種高精度乘法的擴展算法,實質為高精度乘低精度,算法如下: var

a:array[1..10000] of longint;

i,j,k,l,p,o,q,x,y,w:integer;

begin

read(i);

a:=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包含一個低精度轉高精度的初始化函數,可以自動被編譯器調用,因此無需單獨寫高精度數和低精度數的運算函數,十分方便;還包含了一個在各類運算中經常用到的進位小函數。

輸入輸出

平淡無奇,有很多讀入的方法。這里偷個懶,直接讀入一個字符串再轉入Wint中。

大小比較

比較,只需要寫兩個,其他的直接代入即可。值得注意的是,這里用常量引用當參數,避免拷貝更高效。

加法

加法,先實現+=,這樣更簡潔高效。注意各個參數有別。

減法

減法,返回差的絕對值,由于后面有交換,故參數不用引用。

乘法

乘法不能先實現*=,原因自己想。

除法和取模

除法和取模先實現一個帶余除法函數。當然,高精度除法也可以用二分中華人民共和國檔案法實現,不過效率過低且代碼冗長,這里使用常規豎式除法。

使用

通過重載運算符,還可以實現++、--、^、!、邏輯運算符等很多運算,十分簡單,此處都不寫了。

此時你幾乎可以像int一般便捷地使用Wint,甚至可以把Wint和int混合使用。

順手實現一個快速冪,可以看到和普通快速冪幾乎無異。

優化與改進

上述高精度代碼已經能滿足正常使用需求了,不過仍然有優化和改進的空間:

一、萬進制優化

用int保存個位數顯然太過浪費,short型運算效率又沒有int型高(絕大部分機器對int有特別優化),在這樣的情況下,我們將改進后的Wint每位保存十進制下的四位(首位可能有前導0)即萬進制。在這樣的優化下,空間占用四分之一,加法快4倍,乘法16倍,而除法可達64倍之多。當然,這仍屬于常數級優化,不過底層運算十分頻繁的情況下還是值得考慮的。上述代碼無需作出太大調整,只需輸入輸出、進位、減法除法等函數略加改進即可,代碼略。

二、低精度優化

前面說過,目前高精度和低精度的運算會先將低精度提升到高精度再進行運算,這就有了優化空間。注意,這里的優化低精度是基于Wint是由int組成這一特點進行的,大于int型的別的類型(如long long)仍需提升到Wint再運算。考慮到低精度數位數在十位以內,優化后效率提升其實不到十倍。不過,在高精度和低精度混合運算十分頻繁的情況下,專門寫優化的高精度和低精度的運算也聊勝于無。這里先給出加法的優化示例。

三、初始化函數改進:

注意到初始化函數Wint(int n)會將大于int表示范圍的類型數如(unsigned long long)先轉為低精度再初始化,我們再增加(或者直接替換原先的)初始化函數,改為:

此外,在代碼中如果想定義到一個高精度常量(20位向上),就必須增加一個字符串初始化函數而不能直接賦一個整型常數(想想為什么?)。但是,這個字符串初始化函數我們希望它不會像int型一樣在運算中自動提升至Wint。否則,像s+7這樣的表達式就會有意義(先將s隱形轉換至Wint再進行加法運算,返回一個Wint型結果),但通常不符合我們的預期而僅僅只是代碼錯誤,而編譯時無法找出,會給我們調試代碼帶來很大麻煩。所以,這個字符串初始化函數前需加關鍵字explicit,來指出我們不希望隱式轉換的發生。

參考資料 >

生活家百科家居網