Sai số dấu phẩy động (Float Precision)
Vấn đề
Phần tiêu đề “Vấn đề”Số thực (float, double) được lưu trữ theo chuẩn IEEE 754, vốn không thể biểu diễn chính xác hầu hết các số thập phân - đây là hạn chế của mọi ngôn ngữ lập trình, không riêng C++:
#include <iostream>
int main() { double a = 0.1; double b = 0.2;
std::cout << (a + b) << std::endl; // 0.3 (khi in ra, thường được làm tròn hiển thị) std::cout << (a + b == 0.3) << std::endl; // 0 (false) !!
return 0;}Dù std::cout hiển thị 0.3, giá trị thực sự được lưu trữ bên trong là một số rất gần 0.3 nhưng không hoàn toàn bằng (ví dụ 0.30000000000000004).
So sánh trực tiếp == là cạm bẫy
Phần tiêu đề “So sánh trực tiếp == là cạm bẫy”double result = 0.1 * 3;if (result == 0.3) { // GẦN NHƯ LUÔN sai, dù kết quả "trông giống" 0.3 std::cout << "Bang nhau" << std::endl;} else { std::cout << "Khac nhau!" << std::endl; // Dòng này thường được in ra}Giải pháp: so sánh với sai số cho phép (epsilon)
Phần tiêu đề “Giải pháp: so sánh với sai số cho phép (epsilon)”#include <cmath>#include <iostream>
bool is_equal(double a, double b, double epsilon = 1e-9) { return std::fabs(a - b) < epsilon;}
int main() { double a = 0.1 + 0.2; double b = 0.3;
std::cout << is_equal(a, b) << std::endl; // 1 (true) - đúng như mong đợi
return 0;}epsilon là một sai số rất nhỏ chấp nhận được - nếu hiệu của hai số nhỏ hơn epsilon, coi như chúng “bằng nhau” về mặt thực tế.
Tích lũy sai số qua nhiều phép tính
Phần tiêu đề “Tích lũy sai số qua nhiều phép tính”double total = 0.0;for (int i = 0; i < 10; i++) { total += 0.1;}std::cout << total << std::endl; // Có thể là 0.9999999999999999, không phải 1.0!std::cout << (total == 1.0) << std::endl; // Rất có thể là 0 (false)Sai số nhỏ có thể tích lũy dần qua nhiều phép tính, đặc biệt trong vòng lặp - đây là lý do các phần mềm tài chính thường tránh dùng float/double cho tiền tệ, thay vào đó dùng số nguyên (ví dụ lưu theo đơn vị xu thay vì đồng) hoặc kiểu số thập phân có độ chính xác cố định (fixed-point).
Tóm tắt
Phần tiêu đề “Tóm tắt”Số thực không thể biểu diễn chính xác hầu hết giá trị thập phân, do cách lưu trữ theo chuẩn IEEE 754.
- Không bao giờ so sánh trực tiếp hai số thực bằng
==- Dùng một khoảng sai số cho phép (
epsilon) khi so sánh- Với tiền tệ hoặc dữ liệu cần độ chính xác tuyệt đối, cân nhắc dùng số nguyên thay vì số thực