CẨN THẬN KHI SỬ DỤNG KIỂU SỐ THỰC FLOAT

Là một lập trình viên thì thứ mà bạn phải làm việc hàng ngày nhiều nhất chính là biến và các kiểu dữ liệu. Một trong những kiểu dữ liệu mà chắc chắn ai cũng đã phải dùng tới nó là kiểu float. Tuy nhiên, bạn có chắc là mình hiểu rõ kiểu dữ liệu này và đã sử dụng nó một cách đúng đắn chưa? Hãy cùng tìm hiểu kiểu biến float để xem bạn hiểu rõ nó tới đâu nhé.

ĐẶT VẤN ĐỀ

Hãy xem xét đoạn code dưới đây

RUN ONLINEGDB

Thử đoán xem đoạn code trên in ra những gì? Có phải là 2 dòng “Obvious” hay không?

Tất nhiên không phải là 2 dòng “Obvious” rồi, nếu kết quả dễ đoán vậy thì làm gì có bài viết này phải không!? Tôi biết bạn đang bàng hoàng nhưng hãy bình tĩnh lại và cùng tôi phân tích xem chuyện quái gì đang xảy ra ở đây.

TÌM HIỂU NGUYÊN NHÂN

Đem đoạn code ở trên vào trong Visual Studio để debug xem thử giá trị thực của x và z là bao nhiêu, chúng ta có:

Kết quả thật bất ngờ, x và y không phải là 0.20.7 như chúng ta nghĩ. Tại sao là có kết quả lạ lùng như thế này? Rõ ràng chúng ra đã gán x = 0.2 và y = 0.7 cơ mà!?

Giá trị của x và y có sai số như thế là do cách mà số thực được lưu trong phần cứng. Mà dữ liệu trong phần cứng thì được lưu dưới dạng nhị phân và khi biểu diễn số thực dưới dạng nhị phân có thể sẽ có sai số.

Xem thêm: Is floating point math broken?

Để dễ hiểu hơn, chúng ta hãy thử chuyển đổi số 0.2 sang kiểu nhị phân, cách làm như sau

1. Nhân số cần chuyển với số 2

2. Ghi nhớ phần nguyên của kết quả ở bước một. Ví dụ 0.2 x 2 = 0.4 => Ghi nhớ số 0.

3. Lấy phần lẻ của kết quả ở bước 1 làm đầu vào cho bước một. Ví dụ 0.2 x 2 = 0.4. Lấy 0.4 làm đầu vào cho bước một.

4. Lặp lại từ bước một tới bước ba cho tới khi kết quả nhân với 2 có phần lẻ là số 0 hoặc các bước lặp lại vô tận thì dừng lại

0.2 * 2 = 0.4 -> 0

0.4 * 2 = 0.8 -> 0

0.8 * 2 = 1.6 -> 1

0.6 * 2 = 1.2 -> 1

0.2 * 2 = 0.4 -> 0

0.4 * 2 = 0.8 -> 0

0.8 * 2 = 1.6 -> 1

0.6 * 2 = 1.2 -> 1

Kết quả là 0.2 (dec) = 0.0011(0011) (binary)

Các bạn thấy đấy, 0.2 khi chuyển sang kiểu nhị phân sẽ là một số lặp lại vô cùng, và do đó 0.2 được lưu trong bộ nhớ chính xác không phải là 0.2 mà là 0.2000000003 trong trường hợp này. Tương tự đối với 0.7 cũng vậy, nó sẽ là 0.6999999988.

Khoan! Có gì đó sai sai ở đây!? Nếu như vậy thì số 0.7 trong phép so sánh z < 0.7 cũng phải là 0.6999999988 chứ? Có nghĩa là chương trình phải in ra 2 dòng “Obvious” mới đúng chứ?

Trong Visual Studio, di chuyển con chuột tới vị trí của số 0.7, một dòng thông tin sẽ hiện ra như hình bên dưới:

Hoá ra là 0.7 đã được Implicit conversions sang kiểu double. Mà kiểu double có size 8 byte, còn kiểu float chỉ là 4 byte. Có nghĩa là khi lưu giá trị với kiểu double sẽ chính xác hơn và ít sai số hơn. Nói cách khác, số 0.7 lưu dưới dạng double sẽ gần bằng 0.7 hơn là lưu dưới kiểu float.

Nguyên nhân đã rõ, vậy với đoạn code ở đầu bài thì chúng ta phải fix như thế nào để chương trình in ra những gì mà mình mong muốn!?

GIẢI PHÁP

Rất đơn giản, chỉ cần nói cho compile biết là số 0.20.7 là kiểu float. Chúng ta có thể thêm ký tự “F” đằng sau 2 số đó (0.7F) hoặc ép kiểu float: float(0.7). Lúc này phép so sánh if (x < float(0.7)) sẽ là phép so sánh 2 số float với nhau và chương trình in ra 2 dòng “Obvious” như chúng ta mong đợi.

KẾT LUẬN

Lập trình với C giống như bạn đang hẹn hò với một cô gái vậy. Lúc tưởng chừng như bạn đã hiểu rõ cô ấy rồi chính là lúc bạn nhận ra rằng mình chẳng hiểu gì về cô ấy cả! Vậy nên hãy cẩn thận nhé! 😀

 

Bài viết liên quan