Nhng vn đề vi tính toán du chm động
Câu chuyn ca chúng ta bt đầu vi đon chương trình Visual Basic
(VB) sau:
Dim a As Double, b As Double, s As Double
a = 0.11
b = 0.1
s = 0.21
If s = a + b Then
MsgBox "True"
Else
MsgBox "False"
End If
Khi chy, chúng ta s nhn được thông báo sau:
Rõ ràng s = a+b vy mà chương trình li báo "False" (sai). Phi chăng đó là li ca Visual Basic?
Bn nào có ý nghĩ như vy hãy th li vi Java - mt ngôn ng khác hn và hoàn toàn không
phi do Microsoft cung cp - s thy "li" đó lp li ging ht.
Tr v vi đon chương trình VB ban đầu, nếu đổi các biến a, b, s thành kiu Currency thì
chương trình li "chy đúng" như mong đợi (tc là đưa ra thông báo "True"). Dài dòng như vy
để các bn thy gc r ca vn đề nm đặc đim ca s du chm động nh phân và các phép
toán liên quan ti chúng.
Không ít lp trình viên chu bó tay không lý gii được hin tượng trên. Điu khá ngc nhiên là
các s du chm động hin din khp mi nơi trong các h thng máy tính vy mà nhiu người
vn không biết hay không quan tâm ti chúng. Hu hết tt c các ngôn ng lp trình đều có kiu
d liu du chm động; các h thng đin toán t máy PC đến siêu máy tính đều có b gia tc
du chm động; hu hết các trình biên dch đều phi thc hin vic dch các thut toán du chm
động mt các thường xuyên và hu như tt c các h điu hành đều phi đối mt vi nhng
trường hp ngoi l ca du chm động (chng hn b tràn b nh).
Mt s du chm động là cách biu din cho mt s trong mt tp con các s hu t và thường
dùng trong máy tính để biu din gn đúng mt s thc bt k. Mt cách c th hơn, s du
chm động được th hin như mt s nguyên hay s du chm tĩnh (phn có nghĩa hay phn định
tr) nhân vi mt cơ s (thường là cơ s 2 đối vi máy tính) lũy tha (s mũ) nguyên nào đó.
a = m × be
Trong mt h thng như vy, chúng ta chn cơ s b và độ chính xác p (s ch s được lưu). m
(phn có nghĩa hay phn định tr) là mt s có p ch s được biu din dưới dng ±d.ddd...ddd
(mi s là mt s nguyên t 0 đến b-1). Nếu s đầu tiên ca m khác 0 thì nó được coi là đã chun
hóa. Mt s cách mô t có s dng mt bit du riêng (s, thay thế cho -1 hoc +1) và m buc phi
là s dương, e được gi là s mũ.
Mô hình này cho phép biu din mt gii s khá ln trong mt s bit nh mà cách biu din du
chm tĩnh không th hin được. Ví d, mt s du chm động vi 4 s phn thp phân (b = 10, p
= 4) và s mũ trong khong ±4 có th dùng để biu din các s 43210, 4.321 hay 0.0004321,
nhưng s không đủ chính xác để biu din cho s 432.123 và 43212.3 (và do đó s được làm tròn
đến 432.1 và 43210). Trong thc tế s các ch s thường ln hơn 4.
Ngoài ra, vic biu din du chm động thường bao gm nhng giá tr đặc bit: dương vô cc,
âm vô cc và NaN (Not a Number - không phi s). Các giá tr vô cc được s dng khi kết qu
quá ln không th biu din được, còn NaN dùng để kết qu ca nhng phép tính không hp l
hoc kết qu không được định nghĩa.
Trong ví d trên, các con s được biu din trong h thp phân (b = 10), h thng máy tính cũng
thc hin như vy trong h nh phân (b = 2). Trong máy tính, s du chm động được xác định
kích thước bng s bit dùng để lưu tr chúng. Kích thước này là 32 bit hoc 64 bit, thường gi là
độ chính xác đơn (single-precision) và độ chính xác kép (double-precision). Cũng có mt s h
thng đưa ra nhng kích thước ln hơn như 80 bit (dòng x86) hay 128 bit (thường được thc
hin bng phn mm). Bn có th xem cách biu din du chm động nh phân ca các s thp
phân ti trang web http://babbage.cs.qc.edu/courses/cs341/IEEE-754.html.
Thut ng du chm động xut phát t thc tế rng không có s xác định các ch s trước hoc
sau du chm (du chm thp phân có th di chuyn được). Cách biu din trong đó s các ch
s trước và sau du chm thp phân c định gi là du chm tĩnh. Nhìn chung, cách biu din
bng du chm động chm hơn và thiếu chính xác hơn so vi cách biu din du chm tĩnh,
nhưng li có th biu din được mt vùng s ln hơn.
Mt phép toán du chm động là mt phép toán s hc được thc hin vi s du chm động và
thường bao hàm c phép tính gn đúng hay phép làm tròn bi vì kết qu ca mt phép toán có
th được biu din mt cách không chính xác. Do các phép toán vi s du chm động đòi hi
nhiu năng lc đin toán nên nhiu b vi x lý thường đi kèm vi mt chip b sung FPU
(floating point unit) chuyên dùng cho các phép toán du chm động mà chúng ta thường gi là
b đồng x lý toán hc.
RC RI S DU CHM ĐỘNG NH PHÂN
1. Li làm tròn
Vì s du chm động nh phân không th biu din chính xác các s thp phân nên vic s dng
chúng s không th đảm bo cho kết qu như khi s dng các phép toán thp phân. Điu này gây
rt nhiu khó khăn cho vic phát trin và th nghim các ng dng có s dng d liu thc như
thương mi hay tài chính. Dưới đây là mt vài ví d đin hình.
Ly s 1 chia liên tc cho 10 s cho kết qu như bng dưới đây:
Thp phân Nh phân
0.1 0.1
0.01 0.01
0.001 9.999999E-4
0.0001 9.999999E-5
0.00001 9.999999E-6
0.000001 9.999999E-7
1E-7 9.999999E-8
1E-8 9.999999E-9
1E-9 9.999999E-10
Ct bên trái hin th các kết qu mong đợi hay kết qu nhn được khi dùng BigDecimal class ca
Java, ct bên phi hin th các kết qu nhn được bng vic s dng kiu d liu float ca Java.
Các kết qu nhn được t vic s dng kiu d liu double cũng tương t như vy (s có nhiu
s 0 hay s 9 hơn).
Trong mt chng mc nào đó, nhng vn đề như vy có th được giu đi bng cách làm tròn
nhưng s gây nhm ln cho người dùng. Nhng li này s không b phát hin và tích lũy dn sau
nhiu phép toán cho ti khi sai s quá ln. Ví d dưới đây s dng biến kiu double trong Java
cho thy 0.1 × 8 s cho kết qu khác vi vic cng tám ln 0.1.
Chương trình Kết qu
public class Test{
public static void main(String args[]){
double i, j, k;
i=0.1;
j=i+i+i+i+i+i+i+i;
False
0.7999999999999999
0.8
k=i*8;
System.out.println(j==k?"True":"False");
System.out.println(j);
System.out.println(k);
}
}
Lưu ý: Li trên không th được lp li trong VB cũng như Excel vì trong mt s trường hp
Microsoft không hoàn toàn tuân th chun IEEE 754. Tuy nhiên, chúng ta có th th và thy
Excel đôi khi cũng để l nhng kết qu "k quái". Vi VB, chúng ta thy 0.5-0.4-0.1<>0 nhưng
vi Excel thì 0.5-0.4-0.1=0 mc dù 0.5-0.4-0.1)*1<>0 (c th vi Excel 2003, chúng tôi có kết
qu 2.77556E-17).
Thm chí ch mt phép toán đơn cũng có th đưa ra kết qu rt không mong mun. Chng hn
tính 5% thuế cho mt cú gi đin thoi công cng có giá tr 0.70 đôla và làm tròn đến cent gn
nht. Dùng du chm động nh phân kiu double, kết qu ca 0.70 x 1.05 là
0.73499999999999998667732370449812151491641998291015625; kết qu l ra phi là 0.735
(s được làm tròn thành 0.74) thế nhưng con s này sau khi làm tròn li là 0.73.
Sai s cũng s xut hin khi thc hin phép cng hay tr vi các s chênh lch quá ln. Các bn
có th thư giãn vi mt vài đon lnh VB dưới đây.
Chương trình Kết qu
Dim a As Single, b As Single, c As Single
a = 1000000
b = 0.1
c = a + b
MsgBox "c=" & c & "; c-a=" & c - a
Dim a As Single, b As Single, c As Single
a = 1000000.2
b = 1000000.1
c = a - b
MsgBox "c=" & c
Ngoài ra, chúng ta nên nh rng có nhng quy định ca pháp lut hay các quy định trong ni b
ca các t chc mà các ng dng phi tuân th cht ch v độ chính xác (trong h s thp phân)
và phương pháp làm tròn (cho s thp phân) được dùng để tính toán. Nhng yêu cu này ch
th được tha mãn khi làm vic vi cơ s 10.
Qua nhng ví d trên, chúng ta hc được mt điu quan trng: Hãy cn thn khi s dng các
toán t so sánh (== và !=) để so sánh kiu d liu du chm động. Biu thc:
if (float_expr1 == float_expr2)
s rt ít khi được tha mãn vì rt có th xy ra các li trong phép làm tròn s.
2. Th t thc hin phép toán nh hưởng ti kết qu
Các phép toán s dng h thng s du chm động có 2 đim quan trng khác vi tính toán trong
đời sng thc. Đim lưu ý th nht là tính toán du chm động không có tính kết hp. Có nghĩa
là vi các s du chm động nói chung thì:
(x+y)+z <> x+(y+z)
(x*y)*z <> x*(y*z)
Tính toán du chm động không có tính phân phi. Nghĩa là:
x*(y+z) <> x*z + y*z
Tóm li, th t ca phép toán có th làm thay đổi kết qu ca mt phép toán du chm động.
Điu này rt quan trng trong vic phân tích s hóa khi hai biu thc toán hc tương đương có
th không cho cùng mt kết qu s và mt biu thc này có th chính xác hơn biu thc kia. Ví
d:
(1e100 - 1e100) + 1.0 có kết qu là 1.0
trong khi đó
(1e100 + 1.0) - 1e100 li cho kết qu là 0.0
Hai đon chương trình dưới đây s có th gây ra s ngc nhiên ln hơn nhiu so vi đon
chương trình VB mà chúng ta đã xét phn m đầu:
int main(int argc, char **argv) {
double one = 0.1, two = 0.2, three = 0.3, six = 0.6;
if((one + (two + three)) != six) {
printf("0.1 + (0.2 + 0.3) != 0.6\n");
}
else {
printf("0.1 + (0.2 + 0.3) = 0.6\n");