0.1 + 0.2 !== 0.3). Dưới đây là phân tích vấn đề và các giải pháp.
Nguyên nhân
-
Hạn chế cố hữu của số thực dấu phẩy động nhị phân
Các số thập phân (như0.1) trong hệ nhị phân là các số vô hạn tuần hoàn, khi lưu trữ sẽ bị cắt bớt, gây mất độ chính xác. -
Giới hạn phạm vi số nguyên an toàn
KiểuNumbertrong JavaScript chỉ có thể biểu diễn chính xác các số nguyên trong phạm vi[-2^53 +1, 2^53 -1].
Giải pháp
1. Sử dụng số nguyên (tránh số thực)
Chuyển đổi số thập phân thành số nguyên (ví dụ: dùng "xu" thay vì "đồng"), tính toán xong rồi chuyển lại. Cần chú ý phạm vi giá trị, nếu vượt quá số nguyên an toàn thì sử dụng BigInt.
// Ví dụ: Chuyển đổi tiền tệ sang xu để tính
function toCents(value) {
return Math.round(value * 100);
}
const total = (toCents(0.1) + toCents(0.2)) / 100; // 0.3
// Số nguyên lớn dùng BigInt
const a = BigInt(Math.round(0.1 * 100)); // 10n
const b = BigInt(Math.round(0.2 * 100)); // 20n
const sum = Number(a + b) / 100; // 0.3
2. Sử dụng thư viện tính toán độ chính xác cao
- decimal.js: Tính toán số thập phân dấu phẩy động chính xác.
- big.js: Thư viện nhẹ, phù hợp cho tính toán tiền tệ.
// Sử dụng decimal.js
import Decimal from "decimal.js";
const sum = new Decimal(0.1).plus(0.2).toString(); // "0.3"
3. Kiểm soát độ chính xác hiển thị (toFixed hoặc toPrecision)
Chỉ dùng cho hiển thị kết quả cuối cùng, tránh sử dụng trong các bước tính toán trung gian (có thể gây tích lũy sai số).
console.log((0.1 + 0.2).toFixed(2)); // "0.30" (chuỗi)
4. So sánh với dung sai cho phép
Sử dụng Number.EPSILON hoặc ngưỡng tùy chỉnh.
// Dung sai mặc định
function areEqual(a, b, epsilon = 1e-10) {
return Math.abs(a - b) < epsilon;
}
// Ngưỡng động (theo từng cấp độ lớn)
function dynamicAreEqual(a, b) {
return Math.abs(a - b) < Number.EPSILON * Math.max(Math.abs(a), Math.abs(b));
}
5. Hàm tính toán độ chính xác cao tự định nghĩa
Xử lý cộng, trừ, nhân, chia bằng cách nhân lên thành số nguyên.
// Ví dụ về phép cộng
function add(num1, num2) {
const factor = 10 ** Math.max(getDecimals(num1), getDecimals(num2));
return (num1 * factor + num2 * factor) / factor;
}
// Lấy số chữ số thập phân
function getDecimals(num) {
const str = num.toString();
return (str.split('.')[1] || '').length;
}
console.log(add(0.1, 0.2)); // 0.3
Tổng kết
| Tình huống | Giải pháp khuyến nghị |
|---|---|
| Tính toán đơn giản, phạm vi kiểm soát được | Toán số nguyên hoặc nhân lên thành số nguyên để tính |
| Tính toán tài chính phức tạp | Sử dụng decimal.js hoặc big.js |
| Hiển thị kết quả | toFixed hoặc toPrecision |
| So sánh số thực | Kiểm tra với dung sai |
Tùy theo yêu cầu thực tế để lựa chọn giải pháp. Đối với các tình huống phức tạp, ưu tiên sử dụng các thư viện bên thứ ba đã được kiểm chứng.