Tại sao kết quả của 0.1 + 0.2 lại không bằng 0.3 trong lập trình?
Chắc hẳn lập trình viên JavaScript nào cũng đã từng nghe qua về vấn đề này. Tuy nhiên, đây không chỉ là vấn đề riêng của JavaScript. Tôi đã thử nghiệm với nhiều ngôn ngữ lập trình khác như Python, Java, C/C++ và kết quả đều cho thấy rằng:
0.1 + 0.2 == 0.3 // false
Nguyên nhân cốt lõi
Hầu hết các ngôn ngữ lập trình này tuân theo chuẩn IEEE-754 (https://www.geeksforgeeks.org/ieee-standard-754-floating-point-numbers/) để biểu diễn các số thực dấu phẩy động (floating-point number). Chuẩn IEEE-754 có những hạn chế trong việc biểu diễn chính xác các số này dưới dạng nhị phân (binary).
Ví dụ, khi biểu diễn 1/3 dưới dạng thập phân (decimal), chúng ta gặp khó khăn vì kết quả là 0.333333... không bao giờ dừng lại. Tương tự, các số 0.1 và 0.2 cũng rất khó để biểu diễn chính xác ở dạng nhị phân. Cụ thể, 0.1 (thập phân) được biểu diễn là 0.0001100110011... (nhị phân) và 0.2 (thập phân) là 0.001100110011... (nhị phân), với chuỗi 0011 lặp lại mãi.
Vì những lí do này, khi cộng các số có sai số biểu diễn với nhau, kết quả cuối cùng cũng sẽ mắc phải sai số!
Một điều thú vị: Mặc dù 0.1 + 0.2 == 0.3 trả về false, nhưng 0.1 + 0.1 == 0.2 lại trả về true. Bạn có biết tại sao lại như vậy không? (Đây là một câu hỏi mở dành cho các bạn.)
Dành cho những ai thích thông tin chính xác: Việc JavaScript áp dụng chuẩn IEEE-754 được ghi trong đặc tả ngôn ngữ, bạn có thể tìm hiểu thêm tại đây: https://tc39.es/ecma262/#sec-bibliography
Tại sao vẫn dùng chuẩn IEEE-754?
Xét một cách tổng thể, chuẩn IEEE-754 mang lại sự cân bằng giữa hiệu năng, độ chính xác và tính tương thích (bất kể bộ xử lý hay kiến trúc hệ thống là gì) cho hầu hết các ứng dụng thực tế. Tuy nhiên, chúng ta vẫn cần phải nhận thức về hạn chế của nó và tìm ra những giải pháp phù hợp.
Một vài giải pháp trong JavaScript
-
Làm tròn (Rounding): Sử dụng hàm
toFixed()
. Ví dụ:(0.1 + 0.2).toFixed(1); // "0.3"
Đáng lưu ý rằng
toFixed()
trả về một chuỗi (string). -
Sử dụng thư viện: Các thư viện như
decimal.js
,big.js
,dinero.js
cung cấp giải pháp tính toán với độ chính xác cao hơn.
Bài chia sẻ đến đây là hết. Nếu bạn có những suy nghĩ hay giải pháp khác, hãy cùng để lại bình luận bên dưới nhé.
Tài liệu tham khảo:
- https://0.30000000000000004.com/
- Đây là một đoạn mã ví dụ trên GitHub minh họa cách chuyển đổi giữa số dấu phẩy động đơn độ chính xác (32-bit) theo chuẩn IEEE-754 và các biểu diễn nhị phân và thập lục phân tương ứng - https://gist.github.com/Jozo132/2c0fae763f5dc6635a6714bb741d152f
source: viblo