Tại sao 0.1+0.2 ≠ 0.3!?

Nếu không tin, bạn có thể kiểm chứng bằng cách mở browser ra, bấm F12, rồi nhập vào thử, bạn sẽ thấy như hình dưới:
Đây là một vấn đề nổi tiếng trong tính toán số học về dấu chấm động. Lỗi này xảy ra vì số thực (floating-point numbers) không thể được biểu diễn chính xác trong hệ nhị phân.
Con người chúng ta dùng hệ thập phân để biểu diễn một số bất kỳ bằng tổng một chuỗi các lũy thừa của \(10\). Ví dụ: \(758.44 = 7\times 10^2+5\times10^1+8\times10^0 + 4\times 10^{-1}+4\times10^{-2}\).
Khi chuyển sang hệ nhị phân, một số cũng được biểu diễn bằng tổng một chuỗi các lũy thừa của \(2\):
$$\begin{align*} 9_{10} &= 1001_2 = 1\times 2^3 + 0\times 2^2+0 \times 2^1+1\times 2^0 \\ 1.5_{10} &= 1.1_{2} = 1\times2^0 + 1\times2^{-1}\\ 6.75_{10} &= 110.11_{2} = 1\times2^2+1\times2^1 + 0\times2^0 + 1\times2^{-1} +1\times2^{-2} \end{align*}$$
Các bạn chú ý là trong bài viết này, mình dùng ký hiệu dạng \(X_b\) để biểu đạt một giá trị \(X\) ở hệ đếm \(b\). Ví dụ: \(1001_2\) nghĩa là giá trị \(1001\)ở hệ nhị phân.
Trong hệ \(10\), số \(0.1\) thực chất là một chuỗi xấp xỉ vô hạn \(0.1_{10}=0.00011001100110011..._2\), tương tự, \(0.2_{10}\) cũng là một dãy vô hạn xấp xỉ. Tuy nhiên, trong máy tính, dãy này không vô hạn mà chỉ bị giới hạn (thường là \(32\) bit). Biểu diễn số ở máy tính được dùng theo chuẩn IEEE 754 sử dụng 64 bit, theo đó:
\(0.1_{10} \to 00111101110011001100110011001101_2\) (0x3DCCCCCD)
\(0.2_{10} \to 00111110010011001100110011001101_2\) (0x3E4CCCCD)
\(0.3_{10} \to 00111110100110011001100110011010_2\) (0x3E99999A)
Khi thực hiện \(0.1 + 0.2\) trong máy tính thì \(0.1_{10} + 0.2_{10} = 00111110100110011001100110011011_2\) (0x3E99999B) hay \(0.30000004_{10}\). Vì thế kết quả này khi so sánh với \(0.3_{10}\) (0x3E99999A) ở trên là không khớp với (0x3E99999B) nên máy trả về False. Đó chính là nguyên nhân sâu xa của nghịch lý này.
Subscribe to my newsletter
Read articles from Legos Light directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
