Tại sao 0.1 + 0.2 không bằng 0.3 ?
Chắc hẳn lập trình viên JavaScript nào cũng đã từng nghe qua về vấn đề này, nhưng đây không phải là vấn đề riêng của JavaScript. Tôi đã thử ở cả JavaScript, Python, Java, C/C++... và dường như đa số ngôn ngữ lập trình hiện nay đều cho cùng một kết quả:
0.1 + 0.2 == 0.3 // false
Nguyên nhân cốt lõi
Các ngôn ngữ này tuân theo chuẩn IEEE-754 (https://www.geeksforgeeks.org/ieee-standard-754-floating-point-numbers/) để biểu diễn số thực dấu phẩy động (floating-point number). Chuẩn IEEE-754 có một số hạn chế trong việc biểu diễn chính xác các số này ở dạng nhị phân (binary).
Ví dụ:
Chúng ta rất khó để biểu diễn 1/3 ở dạng thập phân (decimal) một cách chính xác (chỉ xấp xỉ 0.333333...), vì con số 3 cứ lặp lại mãi.
Tương tự, ở chuẩn IEEE-754, 0.1 và 0.2 cũng rất khó để biểu diễn chính xác ở dạng nhị phân. 0.1 (decimal) là 0.0001100110011... (binary) và 0.2 (decimal) là 0.001100110011... (binary), vì chuỗi số 0011 cứ lặp lại mãi.
Vì lẽ đó, khi cộng các số có sai số biểu diễn lại với nhau, kết quả cũng sẽ có sai số!
Một điều thú vị: 0.1 + 0.2 == 0.3 thì bằng false, nhưng 0.1 + 0.1 == 0.2 lại bằng true. Bạn có biết tại sao không? (Đây là một câu hỏi mở dành cho các bạn.)
Dành cho các bạn thích tìm thông tin từ nguồn chính xác: Thì việc JS áp dụng chuẩn IEEE-754 được ghi trong đặc tả ngôn ngữ ở đây, dòng đầu tiên - https://tc39.es/ecma262/#sec-bibliography
Vậy tại sao vẫn dùng chuẩn IEEE-754?
Nhìn một cách tổng quát, 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à có những cách giải quyết phù hợp.
Một vài cách giải quyết trong JavaScript
-
- Làm tròn (Rounding): Sử dụng hàm toFixed(). Ví dụ:
(0.1 + 0.2).toFixed(1); // "0.3"
Lưu ý rằng toFixed() trả về một chuỗi (string).
-
- Sử dụng thư viện: decimal.js, big.js, dinero.js là các thư viện hỗ trợ tính toán với độ chính xác cao hơn.
Bài chia sẻ đến đây là hết. Bạn có suy nghĩ hay giải pháp gì khác, hãy cùng để lại bình luận bên dưới nhé.
Nguồn 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
All rights reserved