Chào các bạn, hôm nay chúng ta sẽ cùng nhau mổ xẻ một chủ đề tuy cũ nhưng không bao giờ hết "hot": kiểu dữ liệu float trong PHP. Nhiều lập trình viên, đặc biệt là các bạn mới vào nghề, thường sử dụng float để lưu trữ mọi thứ liên quan đến số thực. Tuy nhiên, đằng sau sự tiện lợi đó là một "cái bẫy" chết người có thể phá hỏng dữ liệu của bạn, nhất là khi làm việc với API.
Bài viết này sẽ giải thích rõ float là gì, tại sao nó "không đáng tin" và cách xử lý số thực một cách chuyên nghiệp.
Kiểu Float là gì?
Trong PHP, kiểu dữ liệu float (còn gọi là double hay real) được dùng để biểu diễn các số có phần thập phân (số chấm động).
Ví dụ:
$price = 19.99;
$pi_value = 3.14159;
$scientific_notation = 5.972e24; // Cách viết khoa học
Nhìn qua thì có vẻ đơn giản và hữu ích, phải không? float rất tuyệt cho các phép tính khoa học, đồ họa, nơi mà sự chênh lệch một chút không phải là vấn đề lớn. Nhưng khi bạn cần độ chính xác tuyệt đối, float sẽ là cơn ác mộng.
Vấn đề cốt lõi: Tại sao Float không chính xác? 😵
Vấn đề không phải của riêng PHP. Hầu hết các ngôn ngữ lập trình đều dùng chung một tiêu chuẩn để biểu diễn số thực là IEEE 754.
Nói một cách dễ hiểu, máy tính dùng hệ nhị phân (0 và 1) để lưu trữ mọi thứ. Tuy nhiên, có những số thập phân rất đơn giản trong hệ 10 của chúng ta lại trở thành một chuỗi số vô hạn tuần hoàn trong hệ 2.
Ví dụ kinh điển nhất: Lấy 0.1 + 0.2. Bạn nghĩ kết quả là 0.3? Hãy xem PHP nói gì:
$result = 0.1 + 0.2;
echo $result; // Kết quả sẽ là: 0.30000000000000004
Vì không thể lưu trữ giá trị 0.1 và 0.2 một cách chính xác tuyệt đối, máy tính đã làm tròn chúng. Khi cộng lại, sai số nhỏ này lộ ra. Điều này dẫn đến một hệ quả tai hại:
if ((0.1 + 0.2) == 0.3) {
echo "Đúng";
} else {
echo "Sai"; // "Sai" sẽ là thứ được in ra!
}
Đây chính là gốc rễ của mọi vấn đề khi bạn "liều lĩnh" dùng float trong các hệ thống yêu cầu sự chính xác.
Tại sao không nên trả về Float qua API?
Khi bạn xây dựng API, dữ liệu thường được chuyển đổi (serialize) sang định dạng JSON để gửi đi. Đây là lúc float bắt đầu phá hoại.
- Mất mát độ chính xác khi
json_encode: Khi PHP chuyển đổi một sốfloatsang chuỗi JSON, nó sẽ cố gắng biểu diễn giá trị đó. Do sai số vốn có, giá trị trong chuỗi JSON có thể không như bạn mong đợi.- Ví dụ: Giá trị
8.1có thể bịjson_encodechuyển thành8.1000000000000014.
- Ví dụ: Giá trị
- Không nhất quán giữa các nền tảng: Client nhận dữ liệu từ API của bạn có thể được viết bằng JavaScript, Java (Android), hay Swift (iOS). Mỗi ngôn ngữ, mỗi nền tảng lại có cách đọc (parse) và xử lý số
floatkhác nhau.- Hệ quả: Cùng một giá trị
floattừ API, nhưng JavaScript có thể hiểu thànhA, trong khi ứng dụng Android lại hiểu thànhB. Lỗi này cực kỳ khó tìm và sửa.
- Hệ quả: Cùng một giá trị
- Thảm họa với ứng dụng tài chính, thương mại điện tử: Trong các hệ thống tính tiền, hóa đơn, thuế, khuyến mãi... việc sai lệch dù chỉ là
0.000000001cũng là không thể chấp nhận. Sai số sẽ tích tụ qua các phép toán và gây ra thất thoát tài chính nghiêm trọng. Hãy tưởng tượng bạn tính tổng tiền của hàng ngàn đơn hàng, sai số sẽ lớn đến mức nào?
Giải pháp chuyên nghiệp: Xử lý số thực như thế nào? ✅
Để tránh các vấn đề trên, hãy tuân thủ các phương pháp sau.
1. Trả về dưới dạng chuỗi (String) - An toàn nhất
Đây là cách làm an toàn, đơn giản và được khuyến nghị rộng rãi nhất. Thay vì trả về số, hãy trả về một chuỗi biểu diễn số đó. Phía client sẽ nhận chuỗi này và toàn quyền quyết định cách xử lý nó bằng các thư viện chuyên dụng (BigDecimal trong Java/Kotlin, Decimal.js trong JavaScript).
Thay vì:
$data = ['price' => 19.99]; // Kiểu float
Hãy làm:
$data = ['price' => '19.99']; // Kiểu string
echo json_encode($data); // Kết quả: {"price":"19.99"}
Dữ liệu của bạn sẽ được bảo toàn 100%.
2. Dùng số nguyên (Integer) cho đơn vị nhỏ nhất - Tiêu chuẩn vàng cho tài chính 💰
Đây là cách các hệ thống thanh toán lớn vẫn làm. Thay vì lưu giá trị tiền tệ dưới dạng số thực (ví dụ $19.99), hãy lưu nó dưới dạng số nguyên theo đơn vị nhỏ nhất (ví dụ: 1999 cents).
- Mọi phép toán (cộng, trừ, nhân) trên backend đều được thực hiện trên số nguyên, hoàn toàn không có sai số.
- Khi trả về API, bạn chỉ cần gửi số nguyên này đi.
- Phía client sẽ tự định dạng lại để hiển thị cho người dùng (ví dụ: chia cho 100).
Ví dụ:
$priceInCents = 1999; // Tương đương $19.99
$data = [
'amount' => $priceInCents,
'currency' => 'USD',
'unit' => 'cents' // Mô tả rõ đơn vị
];
echo json_encode($data); // Kết quả: {"amount":1999,"currency":"USD","unit":"cents"}
3. Dùng thư viện BC Math (nếu cần tính toán trên backend)
Nếu bạn phải thực hiện các phép toán phức tạp với độ chính xác cao ngay trên PHP, hãy sử dụng extension BC Math. Nó cho phép bạn tính toán trên các số được biểu diễn dưới dạng chuỗi, đảm bảo không mất mát độ chính xác.
$a = '0.1';
$b = '0.2';
$sum = bcadd($a, $b, 2); // Cộng $a và $b, lấy 2 chữ số thập phân
echo $sum; // Kết quả là chuỗi "0.30"
Lưu ý: Ngay cả khi dùng BC Math, kết quả cuối cùng trả về qua API vẫn nên là chuỗi.
Kết luận
Float là một công cụ hữu ích nhưng cũng đầy cạm bẫy. Việc hiểu rõ bản chất và hạn chế của nó là kỹ năng bắt buộc của một lập trình viên chuyên nghiệp. Để đảm bảo tính toàn vẹn dữ liệu, không bao giờ trả về trực tiếp dữ liệu kiểu float qua API. Hãy tập thói quen sử dụng chuỗi hoặc số nguyên. Điều này không chỉ giúp hệ thống của bạn ổn định, chính xác mà còn giúp bạn tránh được những giờ debug không đáng có.
Chúc các bạn code an toàn và hiệu quả!









