Vấnđề tính toánvớicácsốlớn Vấn đề tính toánvớicácsốlớn có ý nghĩa rất lớn trong thực tế. Chẳng hạn, thuật toán mã hoá công khai RSA (do Rivers, Shamir và Adleman viết ra vào năm 1978) sử dụng tới 512 số khoá (thuật toán này có liên quan đến việc phân tích cácsố nguyên tố). Trong nhiều ngành khoa học kỹ thuật khác, chúng ta còn phải sử dụng tới các con sốlớn hơn thế rất nhiều. Trong bài báo này, chúng tôi muốn trình bày với đọc giả đôi nét xung quanh vấn đềtínhtoán với sốlớn qua các phép toán phổ biến như: cộng, trừ, nhân, chia, khai căn, luỹ thừa và kiểm tra tính nguyên tố. Trước hết chúng ta cùng bàn về một số môi trường lập trình để thể hiện chương trình tính toánvớisố lớn: Với ngôn ngữ lập trình Pascal: Nếu sử dụng cấu trúc dữ liệu (CTDL) kiểu chuỗi thì chỉ có thể thao tác vớicácsố trong phạm vi 255 chữ số; nếu sử dụng CTDL kiểu mảng thì có thể thao tác vớicácsốlớn vượt xa giới hạn 255 chữ số gấp nhiều lần, có thể đạt tới hàng ngàn chữ số (lưu ý khi làm việc với CTDL mảng là kích thước của mảng cho phép khai báo một số rất lớn nhưng vẫn bị hạn chế vì bộ nhớ dành cho mảng là 64K); cách làm khác là sử dụng con trỏ để khai thác vùng nhớ Heap của máy tính, bằng cách sử dụng CTDL kiểu mảng động để thao tác vớicácsốlớn tới hàng tỉ con số (vì kích thước mảng động phụ thuộc vào dung lượng Ram thực tế của máy tính). Với ngôn ngữ lập trình Delphi hay Java ta có thể thao tác vớicácsốlớn tới hàng tỉ con số thông qua CTDL kiểu chuỗi hay CTDL kiểu mảng. Ngoài ra, cũng có thể sử dụng các ngôn ngữ thông dụng khác như C/C++ để viết chương trình. Chúng ta tạm thời thoả thuận với nhau rằng: tuỳ thuộc yêu cầu mà lựa chọn môi trường lập trình cũng như CTDL phù hợp. Vấnđề quan trọng là thuật toán thể hiện, phần tiếp theo sẽ trình bày các thuật toán có liên quan. 1.Cộng hai số: Để cộng hai số nguyên a và b ta thực hiện như phép cộng bằng tay hai số thông thường. Tức là thực hiện lần lượt phép tính từ bên phải, sử dụng một biến trung gian để lưu phần dư tại mỗi bước. 2.Trừ hai số: Giả sử cần tìm c = a - b ( a=a 1 a 2 a n ; b=b 1 b 2 b m ; n? m), thuật toán được mô tả qua đoạn chương trình sau: du:=0; for i:= n downto n-m +1 do begin tg:=a i -b i - du; if (tg < 0) then begin du:=1;tg:=tg+10; 1 end; else du:=0; c i :=tg; end; for i:= n-m to 1 do{chuyển đoạn đầu còn lại (nếu có) của a vào c} begin c i := (a i +du ) mod 10; du:=(a+du) div 10; end; if (du>0) then c 0 := du; 3.Nhân hai số: Thuật toán này có thể dễ dàng suy ra từ thuật toán cộng hai số, xin dành cho bạn đọc tự tìm hiểu. 4.Chia hai số: - Cắt lần lượt từng "đoạn" của số bị chia tính từ bên trái (có cộng thêm phần dư của các bước trung gian). - Đem chia các đoạn đó cho số chia bằng phép toán trừ. - Thương tìm được chính là dãy cácsố là kết quả của phép chia các "đoạn" cho số chia (được phát triển dần về phía bên phải). - Phần dư của phép chia chính là "đoạn" còn lại không thể chia được nữa. 5.Khai căn bậc hai của một số nguyên X (kết quả là một số nguyên) : b:=1; l:=<số chữ số của X>; {chia số X thành tập cácsố ai có hai chữ sốtính từ trái, riêng số a1 có thể có một hoặc hai chữ số tuỳ theo l chãn hay lẻ } If ( l div 2 =1) then Begin j:=1; Y[j]:=X[1]; i:=2; End Else 2 Begin i:=1;j:=0; end; While (i<l) do Begin j:=j+1; Y[j]:=X[i]*10+X[i+1]; i:=i+2; end; { khởi tạo giá trị ban đầu} a:=round(sqrt(Y[1]));s1:=Y[1]-a;i:=2; Repeat s2:=s1*10+Y[i] div 10; b:=s2 div (2*a); s2:=s2*10+Y[i] mod 10; i:=i+2; s1:=20*a*b+b*b; a:=a*10+b; s1:=s2-s1; Until (i>=l); Kết quả cần tìm chính là a. Ví dụ: cần tính a= {khởi tạo} Y[1]=1;Y[2]=30; a=1;s1=0; {đoạn thủ tục lặp} s2=3; b=3 div(2*a)=1;i=3; s2=30; s1=21; a=a*10+b=11; s1=9; 3 Ta được kết quả a=11. Để chương trình thực hiện hiệu quả hơn, ta có thể cải thiện thuật toán đi đôi chút (chi tiết về phần cài đặt bạn đọc có thể tham khảo trong chương trình. Nếu muốn tìm hiểu về chương trình chi tiết, bạn có thể liên hệ với toà soạn). 6.Nâng lên luỹ thừa: Giả sử cần tính b = ak. Thông thường ta sử dụng thuật toán sau: b:= 1; for i:=1 to k do b:= b*a ; Như vậy cần phải lặp k bước để tìm kết quả, đây là chi phí quá lớn (đặc biệt khi k lớn tới con số hàng ngàn đơn vị). Ta cải tiến với ý tưởng sau: b:=1; while (k > 0) do if (k mod 2 = 1) then begin b:= b*a; k:=k- 1; end else begin a:=a*a; k:=k div 2; end; Kết thúc vòng lặp ta thu được biến b chứa kết quả cần tìm. Tính đúng đắn của thuật toán xin dành cho bạn đọc tự chứng minh. 7.Kiểm tra tính nguyên tố của một số A: Có nhiều thuật toánđể giải quyết vấnđề này, nhưng cần phải đưa ra được một thuật toán hiệu quả cho bài toánsốlớn ở đây. Cơ sở của thuật toán có thể dựa vào định lý số học sau: "A là số nguyên tố khi A không chia hết cho bất kỳ một số ngyên tố nào trong dãy số: d0=2, d1=3, , dkÊ . Định lý vẫn đúng khi trong dãy có chứa một vài hợp số". Vì vậy, ta chỉ cần kiểm tra tính không chia hết của A cho cácsố di được xây dựng như sau: d0=2, d1=3, d2=5 và dk (k > 2)được tính dựa vào dk-1: 4 +Nếu k lẻ: dk =dk-1 +2 +Nếu k chãn: dk =dk-1 +4 (Ví dụ:d0=2, d1=3, d2=5, d3=7, d4=11, d5=13, d6=19? ). Nếu xây dựng đầy đủ các phép toán này, ta sẽ có một công cụ cá nhân khá hoàn chỉnh cho phép chúng ta thao tác vớisố lớn. Thí dụ minh hoạ: Chia hai số nguyên trên ngôn ngữ lập trình Pascal. Dữ liệu vào được cho trong tệp CHIA.INP gồm hai dãy số theo thứ tự là số bị chia và số chia. Mỗi dãy gồm nhiều dòng, mỗi dòng có độ dài không lớn hơn 80 kí tự số. Hai dãy cách nhau một dòng trống. Dữ liệu ra được ghi vào tệp CHIA.OUT gồm hai dãy số theo thứ tự là thương số và phần dư của phép chia. Quy tắc ghi hai dãy số này cũng tương tự như trên. Ví dụ: CHIA.INP CHIA.OUT 40 2 15 10 Toàn bộ chương trình như sau: Program Phep_Chia_Hai_So_Lon; var a,b,c:array[0 25000] of shortint; l,la,lb,lc:integer; Function ss(d,k:integer):boolean; {so sanh hai day so: a[d] a[k]va b[1] b[lb]} var ok,kt:boolean; i,j: integer; begin If k-d+1>lb then ok:=true else if (k-d+1<lb )or (k>la) then ok:=false else begin ok:=true;kt:=true;i:=d;j:=1; while kt and(i<=k) do if a[i] >b[j] then kt:=false else if a[i] <b[j] then 5 Begin kt:=false; ok:=false End else Begin i:=i+1;j:=j+1;end; end; ss:=ok end; Procedure tru(d,k:integer); var tg,phu:byte;j,i:integer; begin phu:=0;j:=lb; for i:=k downto d do begin if a[i]-b[j]- phu<0 then begin a[i]:=a[i]+10-b[j]-phu; phu:=1; end else begin a[i]:=a[i]-b[j]-phu; phu:=0; end; j:=j-1; end; end; Procedure xu_ly; var t:byte;k,i:integer; begin 6 lc:=0;l:=1;k:=lb; if not ss(l,k)and(k>=la) then exit; if not ss(l,k) then k:=k+1; k:=k-1; repeat k:=k+1; while (a[l]=0) and(l<k) do l:=l+1; t:=0; while ss(l,k) do begin tru(l,k);t:=t+1; if (a[l]=0) and(l<=la) then l:=l+1; end; lc:=lc+1;c[lc]:=t; until not ss(l,la)or(k>la); while (k<la) do begin lc:=lc+1;c[lc]:=0;k:=k+1; end; while (l<la)and(a[l]=0) do l:=l+1; end; Procedure init; var f:text;s:string;x,i,code:integer; Begin assign(f,'chia.inp'); reset(f);la:=0;lb:=0; repeat readln(f,s); for i:=1 to length(s) do begin 7 val(s[i],x,code); la:=la+1;a[la]:=x; end; until s=''; repeat readln(f,s); for i:=1 to length(s) do begin val(s[i],x,code); lb:=lb+1;b[lb]:=x; end; until s=''; close(f);b[0]:=0; end; Procedure test; var f:text;i:integer; begin init; xu_ly; assign(f,'chia.out'); rewrite(f); if lc=0 then writeln(f,0) else begin for i:=1 to lc do begin write(f,c[i]); if i mod 60=0 then writeln(f); end; if i mod 60<>0 then writeln(f); end; 8 writeln(f); if l>la then writeln(f,0) else for i:=l to la do begin write(f,A[i]); if i mod 60=0 then writeln(f); end; close(f); end; Begin Test; End. Có thể sửa đổi chương trình một chút để thực hiện phép chia cácsố nguyên và có kiểm tra dữ liệu vào. Dưới đây là một test khác để kiểm tra cùng kết quả thu được: CHIA.INP4432643724756434638462378642634623894689168846166426468146846 88644326437247564346384623786426346238946891688461664264681468468864 432643724756434638462378642634623894689168846166426468146846886 4432643724756434638462378642634623894689168846166426468146846886 CHIA.OUT 100000000000000000000000000000000000000000000000000000000000 000010000000000000000000000000000000000000000000000000000000 000000001 0 9 . Program Phep_Chia_Hai _So_ Lon; var a,b,c:array[0 25000] of shortint; l,la,lb,lc:integer; Function ss(d,k:integer):boolean; {so sanh hai day so: a[d] a[k]va. f:text;s:string;x,i,code:integer; Begin assign(f,'chia.inp'); reset(f);la:=0;lb:=0; repeat readln(f,s); for i:=1 to length(s) do begin 7 val(s[i],x,code); la:=la+1;a[la]:=x;