Các vấn đề về độ chính xác trong JavaScript và giải pháp

Vấn đề độ chính xác trong JavaScript bắt nguồn từ việc nó tuân theo chuẩn số thực dấu phẩy động độ chính xác kép IEEE 754, khiến một số số thập phân không thể được biểu diễn chính xác (ví dụ: 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

  1. 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.

  2. Giới hạn phạm vi số nguyên an toàn
    Kiểu Number trong 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.

Thẻ: JavaScript IEEE 754 độ chính xác số decimal.js big.js

Đăng vào ngày 8 tháng 7 lúc 04:56