Bỏ qua để đến nội dung

Sai số dấu phẩy động

floatdouble lưu trữ số thực theo chuẩn dấu phẩy động IEEE 754 - không thể biểu diễn chính xác tuyệt đối hầu hết số thập phân, dẫn đến những kết quả trông “sai” một cách khó hiểu:

System.out.println(0.1 + 0.2); // 0.30000000000000004 - KHÔNG phải 0.3!
double price = 0.1;
double total = price + price + price;
System.out.println(total == 0.3); // false - dù "trông có vẻ" phải bằng nhau!

Máy tính lưu trữ số thực dưới dạng nhị phân. Một số phân số thập phân đơn giản như 0.1 không có biểu diễn nhị phân hữu hạn chính xác (giống như 1/3 không viết hết được dưới dạng thập phân) - Java phải làm tròn đến giá trị gần nhất có thể biểu diễn được, gây ra sai số rất nhỏ tích lũy qua các phép tính.

Sai số này đặc biệt nguy hiểm khi:

  • So sánh bằng nhau (==) giữa hai số thực - gần như không bao giờ nên làm điều này
  • Tính toán liên quan đến tiền tệ - sai số nhỏ tích lũy qua nhiều giao dịch có thể gây chênh lệch đáng kể
double balance = 0;
for (int i = 0; i < 10; i++) {
balance += 0.1;
}
System.out.println(balance); // 0.9999999999999999 - KHÔNG chính xác là 1.0!

1. Không bao giờ so sánh == giữa hai số thực - so sánh với một sai số cho phép (epsilon):

double a = 0.1 + 0.2;
double b = 0.3;
double epsilon = 1e-9;
System.out.println(Math.abs(a - b) < epsilon); // true - so sánh "gần bằng nhau"

2. Dùng BigDecimal cho các tính toán cần độ chính xác cao, đặc biệt là tiền tệ:

import java.math.BigDecimal;
BigDecimal price1 = new BigDecimal("0.1");
BigDecimal price2 = new BigDecimal("0.2");
BigDecimal total = price1.add(price2);
System.out.println(total); // 0.3 - CHÍNH XÁC

⚠️ Khi tạo BigDecimal, luôn dùng constructor nhận chuỗi (new BigDecimal("0.1")), không dùng double (new BigDecimal(0.1)) - vì 0.1 dưới dạng double đã bị làm tròn từ trước, sai số sẽ “kế thừa” vào BigDecimal.

float/double không thể biểu diễn chính xác hầu hết số thập phân - phép tính có thể cho kết quả sai số rất nhỏ.

  • Không bao giờ so sánh == giữa hai số thực - dùng sai số cho phép (epsilon)
  • Dùng BigDecimal (khởi tạo từ chuỗi) cho tính toán cần độ chính xác cao, đặc biệt là tiền tệ