# 33: Các số dấu phẩy động không phải số thực

Số dấu phẩy động (Floating-point numbers) không phải là một dạng “số thực” như trong cách nói của Toán học, cho dù chúng vẫn được gọi là “thực” trong một số ngôn ngữ lập trình, như là Pascal hay Fortran. Các số thực có độ chính xác tuyệt đối, và do đó chúng luôn liên tục và không bị sai số; nhưng những số dấu phẩy động có tính chính xác nhất định, vì vậy chúng khá hạn chế, và được coi như là những số nguyên “kém cỏi”.

Để minh họa cho vấn đề này, ta gán thử 2147483647 ( ố nguyên lớn nhất trong hệ 32-bit) cho một biến kiểu float 32-bit (x,say), và in nó lên màn hình. Bạn sẽ nhận được số 2147483648. Giờ thì hãy in lên màn hình x — 64. Vẫn là con số 2147483648. Tiêp tục hãy thử in lên x — 65 và bạn sẽ có được số 2147483520! Tại sao vậy nhỉ? Bởi vì khoảng trống ở giữa các biến float trong dãy đó là 128, và thao tác của dấu phẩy động chính là làm tròn số dấu phẩy động.

Những số dấu phẩy động IEEE là những số có độ chính xác cố định dựa trên hai kiểu kí tự khoa học: 1.d1d2…dp-1 × 2e, mà p là sự chính xác (24 cho kiểu float và 53 cho kiểu double). Khoảng cách giữa hai số liên tiếp là 21-p+e, xấp xỉ bằng ε|x|, ε chính là Machine Epsilon (giới hạn trên của sai số tương đối do làm tròn trong số học điểm nổi) (21-p).

Am hiểu về sự phân bố trong số dấu phẩy động có thể giúp bạn tránh được những sai số thường gặp. Nếu bạn đang thực hiện một vòng lặp, ví dụ khi tìm nghiệm của phương trình, thật là vô lý khi bạn yêu cầu độ chính xác cao hơn trong độ sai số mà hệ thống số có thể biểu diễn. Vì vậy, hãy chắc chắn rằng giới hạn chính xác bạn yêu cầu không nhỏ hơn khoảng cách đó, bằng không thì bạn sẽ thực hiện một vòng lặp vô hạn.

Từ lúc số dấu phẩy động được xem như gần giống với số thực, chắc hẳn vẫn sẽ có một chút sai sót. Sai sót này gọi là roundoff (Làm tròn số), và có thể sẽ dẫn đến một vài kết quả rất bất ngờ. Khi bạn thực hiện phép trừ hai số gần bằng nhau, các số có ý nghĩa quan trọng nhất sẽ triệt tiêu lẫn nhau, sau đó chữ số ít quan trọng nhất (nơi xảy ra lỗi sai số) sẽ được đưa lên vị trí quan trọng nhất trong kết quả dấu phẩy động. Về cơ bản thì nó làm lệch đi bất kì hoạt động tính toán nào có liên quan (một hiện tượng được ví như là “sự nhòe (smearing)”). Bạn cần quan sát kĩ hơn các thuật toán của mình để tránh khỏi một tình huống “hủy chương trình một cách thê thảm” (catastrophic cancellation). Để làm rõ hơn, bạn hãy thử giải phương trình x2–100000x + 1 = 0 bằng công thức nghiệm bậc hai. Khi các toán hạng trong biểu thức -b + sqrt(b2–4) gần bằng nhau về độ lớn, bạn có thể tính ra nghiệm r1 = -b + sqrt(b2–4), và sau đó tìm được nghiệm r2 = 1/r1, trong bất kì phương trình bậc hai nào, ax2 + bx + c = 0, các nghiệm đều thỏa mãn r1r2 = c/a.

Hiện tượng Smearing thậm chí có thể xảy ra theo cả những cách tinh tế hơn. Giả sử một library tính ex theo công thức 1 + x + x2/2 + x3/3! + …. Điều này rất ổn cho một nghiệm x dương, nhưng hãy tưởng tượng điều gì sẽ xảy ra khi x lại là một số âm rất lớn. chẵn trong một số dương lớn nếu có trừ cho nhiều phần lẻ cũng không ảnh hưởng đến kết quả. Vấn đề ở đây là trong việc làm tròn một số lớn, phần dương trong chữ số có vai trò quan trọng hơn nhiều so với

kết quả cuối cùng. Câu trả lời là sẽ tiến tới dương vô cực Cách giải quyết cũng rất đơn giản: với x âm, ta tính được ex = 1/e|x|.

Rõ ràng là bạn không nên sử dụng số dấu phẩy động cho các ứng dụng tài chính, đó là những gì mà các lớp học ngôn ngữ thuật toán như Python và C# làm việc. Số dấu phẩy động được sử dụng để việc tính toán khoa học được hiệu quả. Nhưng hiệu quả sẽ vô nghĩa nếu không có sự chính xác, vì vậy hãy ghi nhớ nguồn gốc phát sinh các lỗi và code một cách hợp lí!