Một máy tính trực tuyến cho kết quả chính xác cho một phép tính thập phân và một chuỗi các chữ số bất ngờ cho phép tính tiếp theo. Sự khác biệt không phải là ngẫu nhiên và cũng không phải là lỗi phần mềm — đó là hệ quả có thể dự đoán được của cách số học dấu phẩy động biểu diễn các số trong hệ nhị phân. Khi bạn hiểu được hệ thống này, những điều bất ngờ trở nên dễ hiểu: bạn có thể nhận biết ngay kết quả nào sẽ chính xác tuyệt đối và kết quả nào sẽ mang một lỗi nhỏ ẩn giấu.
Tiêu chuẩn đằng sau mọi máy tính dựa trên trình duyệt: IEEE 754
Mọi ngôn ngữ lập trình lớn, và mọi trình duyệt chạy JavaScript, đều lưu trữ số thập phân theo tiêu chuẩn IEEE 754 cho số học dấu phẩy động nhị phân, được Viện Kỹ sư Điện và Điện tử công bố lần đầu năm 1985. Phiên bản hiện tại, IEEE 754-2019, được công bố vào tháng 7 năm 2019. Hầu hết các bộ xử lý dấu phẩy động phần cứng, từ chip máy tính xách tay đến điện thoại thông minh, đều thực thi tiêu chuẩn này.
Máy tính trực tuyến — bao gồm cả máy tính ở trên — sử dụng biến thể cụ thể gọi là độ chính xác kép (binary64): 64 bit cho mỗi số, trong đó 53 bit dành cho chữ số có nghĩa và phần còn lại mã hóa thang đo (số mũ). 53 bit chữ số có nghĩa này cung cấp cho bạn khoảng 15 đến 17 chữ số thập phân có nghĩa về độ chính xác. Đối với hầu hết các mục đích hàng ngày, đó là quá đủ — nhưng định dạng này có giới hạn cấu trúc đôi khi thể hiện dưới dạng kết quả bất ngờ.
Nguyên nhân gốc rễ: phân số nhị phân không thể biểu diễn chính xác mọi số thập phân
Máy tính hoạt động theo cơ số 2. Mọi số được lưu trữ đều được xây dựng từ các lũy thừa của 2: 1/2, 1/4, 1/8, 1/16…. Mọi phân số thập phân có thể biểu diễn dưới dạng tổng hữu hạn các lũy thừa đó — như 0.5 (= 1/2), 0.25 (= 1/4), hoặc 0.125 (= 1/8) — được lưu trữ chính xác. Mọi phân số thập phân khác đòi hỏi một chuỗi nhị phân vô hạn, mà định dạng 64 bit phải làm tròn đến giá trị gần nhất có thể biểu diễn được.
Tình huống này tương tự trực tiếp với ký hiệu thập phân và phân số 3. Bạn không thể viết chính xác 1/3 trong hệ thập phân — nó là 0.333333… vô tận. Bạn làm tròn nó đến một số chữ số nhất định và chấp nhận một lỗi nhỏ. Số học dấu phẩy động cũng làm điều tương tự, nhưng trong cơ số 2, và các phân số gây ra vấn đề khác: 1/5 (= 0.2), 1/10 (= 0.1), và 3/10 (= 0.3) đều là các phân số lặp vô hạn trong hệ nhị phân.

Cách lỗi làm tròn tích tụ qua các phép tính
Một giá trị làm tròn đơn lẻ tạo ra lỗi rất nhỏ — thường ở chữ số thập phân thứ 16 hoặc 17 — nên không thể nhìn thấy trong sử dụng hàng ngày. Vấn đề tăng lên khi nhiều giá trị làm tròn tương tác qua chuỗi các phép toán. Mỗi bước có thể khuếch đại lỗi hoặc vô tình triệt tiêu nó, và kết quả không phải lúc nào cũng có thể dự đoán chỉ bằng cách quan sát.
Một vài mẫu hình thường xuất hiện các chữ số bất ngờ:
- Cộng nhiều số thập phân — tổng của mười giá trị như 0.1 mỗi cái cho
0.9999999999999999thay vì chính xác là 1, vì lỗi làm tròn trong mỗi 0.1 tích tụ lại. - Nhân với số gần 1 —
1.1 × 3trả về3.3000000000000003trong JavaScript; sự ước lượng hơi quá của 1.1 trong nhị phân được khuếch đại bởi phép nhân. - Trừ hai số gần bằng nhau (hủy bỏ thảm họa) — trừ hai giá trị gần nhau có thể mất nhiều chữ số có nghĩa:
1.0000000000000002 - 1làm lộ ra khoảng cách mà bình thường không nhìn thấy được. - Phép toán lặp lại — chia rồi nhân lại với cùng một giá trị không phải lúc nào cũng trả về số ban đầu chính xác, vì mỗi bước đều tạo ra lỗi làm tròn riêng.
Các ví dụ bạn có thể thử trên máy tính ở trên
Máy tính ở trên sử dụng bộ xử lý dấu phẩy động 64 bit gốc của JavaScript — không áp dụng làm tròn thêm ngoài Math.round(result * 1e10) / 1e10, giúp làm mượt lỗi ở chữ số thập phân thứ mười nhưng để lộ các lỗi ở các chữ số sau đó. Điều này làm cho nó là một cửa sổ đáng tin cậy để quan sát hành vi dấu phẩy động thực tế.
0.1 + 0.2→0.30000000000000004(trường hợp nổi tiếng nhất)1.1 × 3→3.30000000000000030.1 × 0.1→0.0100000000000000021 ÷ 3 × 3→1(lỗi triệt tiêu trong trường hợp này)0.5 + 0.25→0.75(chính xác — cả hai đều là lũy thừa của hai)
Lưu ý rằng các kết quả "sạch" xuất hiện chính xác khi các giá trị liên quan là phân số nhị phân chính xác. Các chữ số bất ngờ xuất hiện khi không phải như vậy.
Ý nghĩa giới hạn độ chính xác trong thực tế
Độ chính xác kép IEEE 754 cung cấp cho bạn khoảng 15–17 chữ số thập phân có nghĩa trước khi phải làm tròn. Đối với phần lớn các ứng dụng, độ chính xác này là rất lớn: một phép đo chính xác đến 10 chữ số thập phân, một con số tài chính với 14 chữ số có nghĩa, hoặc một hằng số vật lý dùng trong tính toán kỹ thuật đều nằm trong phạm vi này một cách thoải mái.
Các tình huống mà giới hạn này quan trọng:
- Phần mềm tài chính — các phép tính tiền tệ tích tụ lỗi làm tròn nhỏ qua hàng triệu giao dịch. Hệ thống tài chính sản xuất sử dụng số học điểm cố định (số nguyên đại diện cho từng xu) thay vì dấu phẩy động để tránh hoàn toàn vấn đề này.
- Mô phỏng khoa học — các phương pháp số chạy lâu dài (mô hình khí hậu, động lực học chất lỏng) tích tụ lỗi làm tròn qua hàng triệu bước; các nhà nghiên cứu sử dụng độ chính xác mở rộng hoặc thư viện số chuyên dụng.
- So sánh bằng trong mã nguồn — hỏi "kết quả này có chính xác là 0.3 không?" hầu như luôn sai; cách làm tiêu chuẩn là kiểm tra xem kết quả có nằm trong khoảng dung sai nhỏ quanh 0.3 hay không.
Cách xử lý các bất ngờ của dấu phẩy động
- Đối với các phép tính hàng ngày — lỗi ở chữ số thập phân thứ 16 và không ảnh hưởng thực tế. Hãy tin tưởng kết quả.
- Đối với công việc tài chính trên máy tính thông thường — làm tròn kết quả cuối cùng đến số chữ số thập phân cần thiết (ví dụ, 2 chữ số cho tiền tệ) và chỉ coi giá trị đã làm tròn là có ý nghĩa.
- Để kiểm tra lỗi có phải do dấu phẩy động không — nhập lại biểu thức trong một phiên làm việc mới và so sánh; lỗi dấu phẩy động là xác định, nên kết quả sẽ giống hệt mỗi lần với cùng biểu thức.
- Để xác định đầu vào an toàn — các giá trị là phân số lũy thừa của 2 (0.5, 0.25, 0.125, 0.0625…) hoặc số nguyên luôn được lưu chính xác. Nếu bạn có thể diễn giải lại bài toán theo các giá trị này, bạn sẽ loại bỏ được nguồn lỗi.
Tự kiểm chứng: nhập1.1 × 3vào máy tính ở trên, rồi nhập0.5 × 3. Kết quả đầu tiên cho một chuỗi nhỏ các chữ số bất ngờ; kết quả thứ hai cho một số 1.5 sạch sẽ. Sự tương phản này tóm gọn toàn bộ câu chuyện về dấu phẩy động: đầu vào chính xác nhị phân, kết quả chính xác nhị phân; đầu vào không chính xác nhị phân, làm tròn ở ranh giới độ chính xác.