Tính toán độ chính xác tùy ý (Arbitrary-Precision Arithmetic), còn gọi là tính toán số nguyên lớn (bignum), sử dụng một số cấu trúc thuật toán để hỗ trợ các phép toán giữa những số nguyên rất lớn (độ lớn vượt quá kiểu số nguyên dựng sẵn của ngôn ngữ).
Giới thiệu
Bài toán số học độ chính xác cao có rất nhiều chi tiết nhỏ, cách hiện thực cũng có nhiều điểm cần chú ý.
Vậy hôm nay hãy cùng nhau viết một chiếc máy tính đơn giản nhé.
Nhiệm vụ
Input: một biểu thức dạng a <op> b.
a, b là các số nguyên không âm hệ thập phân, độ dài không quá \(1000\);
<op> là một ký tự (+, -, * hoặc /), biểu thị phép toán;
Số nguyên và toán tử cách nhau bởi một dấu cách.
Output: kết quả phép toán.
Với +, -, * in ra một dòng là kết quả;
Với / in ra hai dòng lần lượt là thương và dư.
Đảm bảo kết quả đều là số nguyên không âm.
Lưu trữ
Trong cách cài đặt thông thường, số lớn được biểu diễn bằng chuỗi, mỗi ký tự là một chữ số thập phân. Vì vậy có thể nói tính toán số lớn thực chất là một dạng xử lý chuỗi đặc biệt.
Khi đọc chuỗi, chữ số cao nhất nằm ở đầu chuỗi (chỉ số nhỏ). Nhưng theo thói quen, ta lưu chữ số thấp nhất ở vị trí chỉ số nhỏ nhất, tức là lưu chuỗi đảo ngược. Lý do là độ dài số có thể thay đổi, nhưng ta muốn các vị trí cùng trọng số luôn thẳng hàng (ví dụ: mọi hàng đơn vị ở chỉ số [0], mọi hàng chục ở chỉ số [1]…); đồng thời, phép cộng, trừ, nhân thường bắt đầu từ hàng đơn vị (nhớ phép tính đặt dọc ở tiểu học), nên “lưu đảo” rất hợp lý.
Từ đây về sau sẽ luôn dùng quy ước này. Định nghĩa hằng LEN = 1004 là độ dài tối đa chương trình chứa được.
Từ đó dễ dàng viết mã đọc số lớn:
1 2 3 4 5 6 7 8 910111213141516
voidclear(inta[]){for(inti=0;i<LEN;++i)a[i]=0;}voidread(inta[]){staticchars[LEN+1];scanf("%s",s);clear(a);intlen=strlen(s);// Như đã nói, đảo ngượcfor(inti=0;i<len;++i)a[len-i-1]=s[i]-'0';// s[i] - '0' chính là chữ số tương ứng// Một số bạn quen hiểu theo ord(s[i]) - ord('0')}
In ra cũng theo thứ tự đảo ngược. Vì không muốn in số 0 ở đầu, ta tìm từ vị trí cao nhất xuống chữ số khác 0 đầu tiên rồi in từ đó; điều kiện dừng i >= 1 thay vì i >= 0 là để khi toàn bộ số bằng \(0\) vẫn in ra ký tự 0.
Trong bốn phép toán, độ khó không giống nhau. Dễ nhất là cộng/trừ số lớn, tiếp theo là nhân số lớn với số thường (int) và nhân số lớn với số lớn, cuối cùng là chia số lớn với số lớn.
Chúng ta sẽ hiện thực theo thứ tự này.
Cộng
Cộng số lớn thực chất là cộng đặt dọc.
Bắt đầu từ hàng thấp nhất, cộng từng cặp chữ số tương ứng rồi kiểm tra có đạt hoặc vượt \(10\) không. Nếu có thì xử lý nhớ: tăng kết quả ở hàng cao hơn lên \(1\), và giảm hàng hiện tại đi \(10\).
1 2 3 4 5 6 7 8 910111213141516
voidadd(inta[],intb[],intc[]){clear(c);// Trong hiện thực số lớn, thường đặt LEN lớn hơn độ dài input// rồi bỏ qua vài vòng lặp cuối để giảm xử lý biên// Vì input tối đa 1000 chữ số, nên lặp đến LEN - 1 = 1003 là đủfor(inti=0;i<LEN-1;++i){// Cộng chữ số cùng vị tríc[i]+=a[i]+b[i];if(c[i]>=10){// Nhớc[i+1]+=1;c[i]-=10;}}}
Thử nhập 1 2 —— kết quả /9999999, ủa OI Wiki sao lại cho code giả vậy…
Thực ra, đoạn code trên chỉ xử lý trường hợp số bị trừ \(a\) lớn hơn hoặc bằng số trừ \(b\). Khi \(a<b\) thì rất đơn giản:
\(a-b=-(b-a)\)
Cần tính \(b-a\) vì \(b>a\) nên gọi sub(b,a,c). Sau đó thêm dấu âm trước kết quả là được.
Nhân
Số lớn — số thường
Nhân số lớn là… khoan đã!
Trước hết xét trường hợp một thừa số là int thường. Có cách đơn giản không?
Trực giác là nhân từng chữ số của \(a\) với \(b\). Về giá trị thì đúng, nhưng không đúng dạng thập phân nên cần chỉnh lại.
Cách chỉnh cũng xử lý nhớ từ thấp lên cao. Nhưng nhớ ở đây có thể rất lớn (tới cỡ \(9b\)), nên không thể chỉ trừ \(10\); phải dùng thương và dư của phép chia cho \(10\). Xem chú thích và hình dưới minh họa \(1337 \times 42\).
Vì lý do này cần chú ý phạm vi của \(b\). Nếu \(b\) cùng cỡ \(10^9\) (giới hạn int), cần cẩn thận với cách nhân số lớn—số thường.
1 2 3 4 5 6 7 8 910111213141516
voidmul_short(inta[],intb,intc[]){clear(c);for(inti=0;i<LEN-1;++i){// Nhân trực tiếp chữ số thứ i với b, cộng vào kết quảc[i]+=a[i]*b;if(c[i]>=10){// Xử lý nhớ// c[i] / 10 là phần thương dùng làm tăng nhớc[i+1]+=c[i]/10;// c[i] % 10 là phần dư giữ lại ở vị trí hiện tạic[i]%=10;}}}
Số lớn — số lớn
Nếu cả hai là số lớn, ta dùng nhân đặt dọc.
Nhớ rằng mỗi bước thực chất tính các tổng \(a \times b_i \times 10^i\). Ví dụ \(1337 \times 42\) là \(1337 \times 2 \times 10^0 + 1337 \times 4 \times 10^1\).
Vì vậy ta tách \(b\) thành từng chữ số, nhân từng chữ số (số thường) với \(a\), dịch trái đến đúng vị trí rồi cộng. Cuối cùng xử lý nhớ như trên.
Lưu ý quá trình này hơi khác nhân đặt dọc: trong mỗi bước không xử lý nhớ, mà dồn lại rồi cuối cùng mới xử lý. Kết quả vẫn đúng.
1 2 3 4 5 6 7 8 9101112131415
voidmul(inta[],intb[],intc[]){clear(c);for(inti=0;i<LEN-1;++i){// Trực tiếp tính chữ số thứ i của kết quả (từ thấp lên cao), kèm xử lý nhớ// Vòng lặp i cộng vào c[i] tất cả tích a[p]*b[q] với p+q=i// Hiệu quả tương đương nhân đặt dọc rồi cộng, nhưng gọn hơnfor(intj=0;j<=i;++j)c[i]+=a[j]*b[i-j];if(c[i]>=10){c[i+1]+=c[i]/10;c[i]%=10;}}}
Chia
Một cách thực hiện chia số lớn là phép chia đặt dọc.
Chia đặt dọc có thể xem như quá trình trừ lặp. Ví dụ hàng chục của thương trong hình: lấy \(45\) trừ \(12\) ba lần vẫn còn \(\ge 12\), lần thứ tư thì < \(12\), nên chữ số là \(3\).
Để giảm tính toán, ta biết trước độ dài bị chia \(l_a\) và chia \(l_b\), bắt đầu từ chỉ số \(l_a-l_b\) và đi từ cao xuống thấp. Điều này giống việc căn thẳng chữ số cao nhất khi chia tay.
Hàm greater_eq() kiểm tra phần bị chia (từ last_dg trở lên) có đủ lớn để trừ tiếp chia hay không. Với mỗi chữ số thương, ta lặp kiểm tra, nếu được thì trừ chia khỏi dư (tức mô phỏng chia đặt dọc).
// Kiểm tra bị chia a, lấy last_dg là chữ số thấp nhất, có thể trừ tiếp b mà vẫn không âm không// len là độ dài của b để tránh tính lạiboolgreater_eq(inta[],intb[],intlast_dg,intlen){// Có thể phần còn lại dài hơn chia 1 chữ số, vậy kiểm tra vậy là đủif(a[last_dg+len]!=0)returntrue;// So sánh từ cao xuống thấpfor(inti=len-1;i>=0;--i){if(a[last_dg+i]>b[i])returntrue;if(a[last_dg+i]<b[i])returnfalse;}// Bằng nhau cũng trừ đượcreturntrue;}voiddiv(inta[],intb[],intc[],intd[]){clear(c);clear(d);intla,lb;for(la=LEN-1;la>0;--la)if(a[la-1]!=0)break;for(lb=LEN-1;lb>0;--lb)if(b[lb-1]!=0)break;if(lb==0){// Chia cho 0 không đượcputs("> <");return;}// c là thương// d là phần còn lại của bị chia, sau khi kết thúc chính là dưfor(inti=0;i<la;++i)d[i]=a[i];for(inti=la-lb;i>=0;--i){// Tính chữ số thứ i của thươngwhile(greater_eq(d,b,i,lb)){// Nếu trừ được thì trừ// Đoạn này là phép trừ số lớnfor(intj=0;j<lb;++j){d[i+j]-=b[j];if(d[i+j]<0){d[i+j+1]-=1;d[i+j]+=10;}}// Tăng chữ số thương lên 1c[i]+=1;// Quay lại kiểm tra}}}
Hoàn thành phần nhập môn!
Kết hợp bốn phép toán trên là xong chương trình máy tính như đầu bài.
Trong cộng, trừ, nhân số lớn, ta tách số thành từng chữ số rồi tính.
Ví dụ \(8192\times 42\) thì thực chất là \((8000+100+90+2)\times(40+2)\).
Khi số có nhiều chữ số, số phần tử tách ra rất nhiều, hiệu năng giảm.
Có thể tối ưu không?
Nhận thấy cách tách không ảnh hưởng kết quả, nên có thể gộp nhiều chữ số lại thành một “chữ số” lớn.
Quá trình
Với ví dụ trên, nếu mỗi “chữ số” gồm 2 chữ số thập phân thì ta tách thành \((8100+92)\times 42\).
Không ảnh hưởng kết quả, nhưng số phần tử giảm nên nhanh hơn.
Từ góc nhìn hệ cơ số, ta thực hiện phép toán trong cơ số lớn hơn (mỗi 2 chữ số tương đương cơ số \(100\)), giúp giảm số “chữ số” tham gia tính toán.
Đó là ý tưởng ép cơ số.
Dưới đây là cộng trong ép cơ số:
Cài đặt tham khảo cho cộng ép cơ số
1 2 3 4 5 6 7 8 910111213
// Các mảng a,b,c đều ở cơ số p// Khi xuất kết quả cần đổi về thập phânvoidadd(inta[],intb[],intc[]){clear(c);for(inti=0;i<LEN-1;++i){c[i]+=a[i]+b[i];if(c[i]>=p){// Với số lớn thường thì p=10c[i+1]+=1;c[i]-=p;}}}
Chia đặt dọc hiệu quả trong ép cơ số
Trong ép cơ số, nếu vẫn thử thương theo cách cũ thì số lần thử rất nhiều. Ví dụ cơ số 10000 thì trung bình mỗi vị trí thử ~5000 lần, không chấp nhận được. Vì vậy cần cách ước lượng thương hiệu quả hơn.
Có thể dùng double làm trung gian. Giả sử bị chia có 4 chữ số \(a_4,a_3,a_2,a_1\), chia có 3 chữ số \(b_3,b_2,b_1\), thì chỉ cần ước lượng một chữ số thương bằng:
\(\dfrac{a_4 base + a_3}{b_3 + b_2 base^{-1} + (b_1+1)base^{-2}}\).
Với nhiều chữ số, lặp lại cách ước lượng một chữ số. Vì dùng 3 chữ số của chia để ước lượng, ta đảm bảo thương ước lượng \(q'\) và thương thực \(q\) thỏa \(q-1 \le q' \le q\). Khi đó mỗi vị trí nhiều nhất thử 2 lần. Tuy nhiên yêu cầu \(base^3 < 2^{53}\) (độ chính xác double), nên khuyến nghị không vượt 32768 cơ số, nếu không dễ sai do lỗi chính xác.
Ngoài ra, vì ước lượng luôn \(\le\) thương thực, còn có thể tối ưu: đa số trường hợp chỉ ước lượng một lần. Khi chuyển sang chữ số tiếp theo, thương có thể vượt base do sai số trước đó, nhưng chỉ cần dồn xử lý nhớ cuối cùng. Ví dụ base=10, tính \(395081/9876\):
Ước lượng \(3950/988=3\), nên \(395081-(9876 \times 3 \times 10^1)=98801\). Có sai số nhưng bỏ qua.
Với dư 98801 ước lượng \(9880/988=10\), nên \(98801-(9876 \times 10 \times 10^0)=41\) là dư cuối.
Cộng kết quả và xử lý nhớ: \(3 \times 10^1 + 10 \times 10^0 = 40\) là thương đúng.
Ý tưởng đơn giản nhưng dễ mắc bẫy. Dưới đây là một cài đặt đã được kiểm chứng:
Cài đặt tham khảo chia đặt dọc hiệu quả trong ép cơ số
// Template đầy đủ và hiện thực https://baobaobear.github.io/post/20210228-bigint1/// Trừ b * mul rồi dịch trái offset, phục vụ chiaBigIntSimple&sub_mul(constBigIntSimple&b,intmul,intoffset){if(mul==0)return*this;intborrow=0;// Khác với phép trừ, borrow có thể rất lớn, không thể dùng cách trừ thườngfor(size_ti=0;i<b.v.size();++i){borrow+=v[i+offset]-b.v[i]*mul-BIGINT_BASE+1;v[i+offset]=borrow%BIGINT_BASE+BIGINT_BASE-1;borrow/=BIGINT_BASE;}// Nếu còn mượn thì tiếp tục xử lýfor(size_ti=b.v.size();borrow;++i){borrow+=v[i+offset]-BIGINT_BASE+1;v[i+offset]=borrow%BIGINT_BASE+BIGINT_BASE-1;borrow/=BIGINT_BASE;}return*this;}BigIntSimplediv_mod(constBigIntSimple&b,BigIntSimple&r)const{BigIntSimpled;r=*this;if(absless(b))returnd;d.v.resize(v.size()-b.v.size()+1);// Tính sẵn nghịch đảo của 3 chữ số cao nhất +1 của b// Nếu 3 chữ số cao nhất là a3,a2,a1// db là 1 / (a3 + a2/base + (a1+1)/base^2), dùng để ước lượng thương// Cách này với BIGINT_BASE<=32768 thì an toàn trong int32// Dù dùng int64, cũng chỉ an toàn khi BIGINT_BASE<=131072 (giới hạn độ chính xác double)// Đảm bảo q'<=q<=q'+1// Nên mỗi chữ số trung bình chỉ thử 1 lần, sau đó dồn xử lý nhớ// Nếu dùng base lớn hơn thì cần cách ước lượng khácdoublet=(b.get((unsigned)b.v.size()-2)+(b.get((unsigned)b.v.size()-3)+1.0)/BIGINT_BASE);doubledb=1.0/(b.v.back()+t/BIGINT_BASE);for(size_ti=v.size()-1,j=d.v.size()-1;j<=v.size();){intrm=r.get(i+1)*BIGINT_BASE+r.get(i);intm=std::max((int)(db*rm),r.get(i+1));r.sub_mul(b,m,j);d.v[j]+=m;if(!r.get(i+1))// Kiểm tra chữ số cao nhất đã về 0 chưa, tránh trường hợp cực đoan--i,--j;}r.trim();// Sửa chữ số hàng đơn vịintcarry=0;while(!r.absless(b)){r.subtract(b);++carry;}// Sửa nhớ cho mỗi chữ sốfor(size_ti=0;i<d.v.size();++i){carry+=d.v[i];d.v[i]=carry%BIGINT_BASE;carry/=BIGINT_BASE;}d.trim();d.sign=sign*b.sign;returnd;}BigIntSimpleoperator/(constBigIntSimple&b)const{BigIntSimpler;returndiv_mod(b,r);}BigIntSimpleoperator%(constBigIntSimple&b)const{BigIntSimpler;div_mod(b,r);returnr;}
Nhân Karatsuba
Gọi số chữ số là \(n\), nhân đặt dọc số lớn cần \(O(n^2)\). Phần này giới thiệu thuật toán tốt hơn do Anatoly Karatsuba đề xuất, là một thuật toán chia để trị.
Xét hai số nguyên lớn \(x,y\) hệ thập phân, đều có \(n\) chữ số (có thể có số 0 đầu). Chọn \(0 < m < n\), đặt
\[
\begin{aligned}
x &= x_1 \cdot 10^m + x_0, \\
y &= y_1 \cdot 10^m + y_0, \\
x \cdot y &= z_2 \cdot 10^{2m} + z_1 \cdot 10^m + z_0,
\end{aligned}
\]
nên để tính \(z_1\) chỉ cần tính \((x_1 + x_0)(y_1 + y_0)\) rồi trừ \(z_2\) và \(z_0\).
Đây là hạt nhân của Karatsuba: bài toán nhân độ dài \(n\) chuyển thành 3 bài toán nhỏ hơn. Lấy \(m = \left\lceil \dfrac n 2 \right\rceil\), nếu \(T(n)\) là thời gian nhân hai số \(n\) chữ số, thì \(T(n) = 3 \cdot T \left(\left\lceil \dfrac n 2 \right\rceil\right) + O(n)\), suy ra \(T(n) = \Theta(n^{\log_2 3}) \approx \Theta(n^{1.585})\).
Có thể hiện thực đệ quy. Để rõ ràng, đoạn mã dưới dùng Karatsuba để nhân đa thức, rồi xử lý nhớ.
int*karatsuba_polymul(intn,int*a,int*b){if(n<=32){// Kích thước nhỏ thì tính trực tiếp, tránh đệ quy làm chậmint*r=newint[n*2+1]();for(inti=0;i<=n;++i)for(intj=0;j<=n;++j)r[i+j]+=a[i]*b[j];returnr;}intm=n/2+1;int*r=newint[m*4+1]();int*z0,*z1,*z2;z0=karatsuba_polymul(m-1,a,b);z2=karatsuba_polymul(n-m,a+m,b+m);// Tính z1// Tạm thời thay đổi, xong thì khôi phụcfor(inti=0;i+m<=n;++i)a[i]+=a[i+m];for(inti=0;i+m<=n;++i)b[i]+=b[i+m];z1=karatsuba_polymul(m-1,a,b);for(inti=0;i+m<=n;++i)a[i]-=a[i+m];for(inti=0;i+m<=n;++i)b[i]-=b[i+m];for(inti=0;i<=(m-1)*2;++i)z1[i]-=z0[i];for(inti=0;i<=(n-m)*2;++i)z1[i]-=z2[i];// Ghép z0, z1, z2 thành kết quảfor(inti=0;i<=(m-1)*2;++i)r[i]+=z0[i];for(inti=0;i<=(m-1)*2;++i)r[i+m]+=z1[i];for(inti=0;i<=(n-m)*2;++i)r[i+m*2]+=z2[i];delete[]z0;delete[]z1;delete[]z2;returnr;}voidkaratsuba_mul(inta[],intb[],intc[]){int*r=karatsuba_polymul(LEN-1,a,b);memcpy(c,r,sizeof(int)*LEN);for(inti=0;i<LEN-1;++i)if(c[i]>=10){c[i+1]+=c[i]/10;c[i]%=10;}delete[]r;}
Tuy nhiên cách này có vấn đề: trong cơ số \(b\), mỗi hệ số đa thức có thể tới cỡ \(n \cdot b^2\), trong ép cơ số có thể tràn số; còn nếu xử lý nhớ trong quá trình nhân đa thức thì \(x_1+x_0\) và \(y_1+y_0\) có thể đạt \(2 \cdot b^m\), tăng thêm một chữ số (nếu dùng \(x_1-x_0\) thì phải xử lý số âm). Do đó cần chọn hiện thực phù hợp với bối cảnh.
Nhân số lớn hiệu quả dựa trên đa thức
Nếu dữ liệu tới cỡ \(10^{10^5}\) hoặc lớn hơn, nhân số lớn thông thường có thể TLE. Phần này giới thiệu cách dùng đa thức tối ưu.
Với số \(a\) có \(n\) chữ số, có thể xem như đa thức \(A=a_{0} 10^0+a_{1} 10^1+\cdots+a_{n-1} 10^{n-1}\) với hệ số nguyên không quá \(10\). Như vậy nhân số nguyên chuyển thành nhân đa thức.
Nhân đa thức thường là \(O(n^2)\), nhưng có thể dùng FFT, NTT để giảm xuống \(O(n\log n)\).
Đóng gói thành lớp
Ở đây có một lớp số lớn đóng gói sẵn, và ở đây là một lớp siêu nhỏ hỗ trợ độ dài động và bốn phép toán.
constexprintMAXN=9999;// MAXN là giá trị lớn nhất của một “chữ số”constexprintMAXSIZE=10024;// MAXSIZE là số chữ sốconstexprintDLEN=4;// DLEN ghi số chữ số gộp vào một “chữ số”structBig{inta[MAXSIZE],len;boolflag;// Đánh dấu dấu '-' cho số âmBig(){len=1;memset(a,0,sizeofa);flag=false;}Big(constint);Big(constchar*);Big(constBig&);Big&operator=(constBig&);Bigoperator+(constBig&)const;Bigoperator-(constBig&)const;Bigoperator*(constBig&)const;Bigoperator/(constint&)const;// TODO: Big / Big;Bigoperator^(constint&)const;// TODO: Big ^ Big;// TODO: Big phép toán bit;intoperator%(constint&)const;// TODO: Big ^ Big;booloperator<(constBig&)const;booloperator<(constint&t)const;voidprint()const;};Big::Big(constintb){intc,d=b;len=0;// memset(a,0,sizeof a);CLR(a);while(d>MAXN){c=d-(d/(MAXN+1)*(MAXN+1));d=d/(MAXN+1);a[len++]=c;}a[len++]=d;}Big::Big(constchar*s){intt,k,index,l;CLR(a);l=strlen(s);len=l/DLEN;if(l%DLEN)++len;index=0;for(inti=l-1;i>=0;i-=DLEN){t=0;k=i-DLEN+1;if(k<0)k=0;g(j,k,i)t=t*10+s[j]-'0';a[index++]=t;}}Big::Big(constBig&T):len(T.len){CLR(a);f(i,0,len)a[i]=T.a[i];// TODO: có cần overload chỗ này?}Big&Big::operator=(constBig&T){CLR(a);len=T.len;f(i,0,len)a[i]=T.a[i];return*this;}BigBig::operator+(constBig&T)const{Bigt(*this);intbig=len;if(T.len>len)big=T.len;f(i,0,big){t.a[i]+=T.a[i];if(t.a[i]>MAXN){++t.a[i+1];t.a[i]-=MAXN+1;}}if(t.a[big])t.len=big+1;elset.len=big;returnt;}BigBig::operator-(constBig&T)const{intbig;boolctf;Bigt1,t2;if(*this<T){t1=T;t2=*this;ctf=true;}else{t1=*this;t2=T;ctf=false;}big=t1.len;intj=0;f(i,0,big){if(t1.a[i]<t2.a[i]){j=i+1;while(t1.a[j]==0)++j;--t1.a[j--];// WTF?while(j>i)t1.a[j--]+=MAXN;t1.a[i]+=MAXN+1-t2.a[i];}elset1.a[i]-=t2.a[i];}t1.len=big;while(t1.len>1&&t1.a[t1.len-1]==0){--t1.len;--big;}if(ctf)t1.a[big-1]=-t1.a[big-1];returnt1;}BigBig::operator*(constBig&T)const{Bigres;intup;intte,tee;f(i,0,len){up=0;f(j,0,T.len){te=a[i]*T.a[j]+res.a[i+j]+up;if(te>MAXN){tee=te-te/(MAXN+1)*(MAXN+1);up=te/(MAXN+1);res.a[i+j]=tee;}else{up=0;res.a[i+j]=te;}}if(up)res.a[i+T.len]=up;}res.len=len+T.len;while(res.len>1&&res.a[res.len-1]==0)--res.len;returnres;}BigBig::operator/(constint&b)const{Bigres;intdown=0;gd(i,len-1,0){res.a[i]=(a[i]+down*(MAXN+1))/b;down=a[i]+down*(MAXN+1)-res.a[i]*b;}res.len=len;while(res.len>1&&res.a[res.len-1]==0)--res.len;returnres;}intBig::operator%(constint&b)const{intd=0;gd(i,len-1,0)d=(d*(MAXN+1)%b+a[i])%b;returnd;}BigBig::operator^(constint&n)const{Bigt(n),res(1);inty=n;while(y){if(y&1)res=res*t;t=t*t;y>>=1;}returnres;}boolBig::operator<(constBig&T)const{intln;if(len<T.len)returntrue;if(len==T.len){ln=len-1;while(ln>=0&&a[ln]==T.a[ln])--ln;if(ln>=0&&a[ln]<T.a[ln])returntrue;returnfalse;}returnfalse;}boolBig::operator<(constint&t)const{Bigtee(t);return*this<tee;}voidBig::print()const{printf("%d",a[len-1]);gd(i,len-2,0){printf("%04d",a[i]);}}voidprint(constBig&s){intlen=s.len;printf("%d",s.a[len-1]);gd(i,len-2,0){printf("%04d",s.a[i]);}}chars[100024];
Last updated on this page:, Update history Found an error? Want to help improve? Edit this page on GitHub! Contributors to this page:OI-wiki All content on this page is provided under the terms of the CC BY-SA 4.0 and SATA license, additional terms may apply