NhaLapTrinh.comNhà Lập Trình
Máy tính chạy thế nào

Vì sao máy tính tính số thập phân lại ra lệch?

Máy tính lưu số theo hệ nhị phân, và nhiều số thập phân quen thuộc như không phẩy một không thể biểu diễn chính xác trong hệ đó, nên phép tính cộng dồn ra lệch rất nhỏ. Với tiền bạc, nên lưu bằng số nguyên theo đơn vị nhỏ nhất và làm tròn theo quy tắc rõ ràng.

🎙️
📑 Nội dung theo chương
  1. 0:40 Một phần ba và không phẩy một
  2. 1:35 Khi một đồng lệch trở thành chuyện lớn
  3. 2:26 Lưu tiền bằng số nguyên
  4. 3:15 Làm tròn cũng là một quyết định
  5. 4:04 So sánh số thập phân sao cho đúng
  6. 4:51 Câu hỏi thường gặp
🔊 Bản audio (lồng nhạc nền)

Máy tính lưu số theo hệ nhị phân, chỉ có số không và số một, và rất nhiều số thập phân quen thuộc như không phẩy một không thể viết chính xác trong hệ đó, giống như một phần ba không thể viết hết trong hệ thập phân. Thế nên cộng dồn nhiều lần sẽ ra lệch một chút. Với tiền bạc, lập trình viên tránh chuyện này bằng cách lưu số nguyên và làm tròn theo quy tắc rõ ràng.

Chào bạn, đây là Nhà Lập Trình. Nhiều người mới học rất sốc khi lần đầu thấy máy tính cộng không phẩy một với không phẩy hai mà ra một con số dài lằng nhằng, gần bằng mà không đúng bằng không phẩy ba. Máy tính mà cũng tính sai à? Tập này mình kể vì sao, và vì sao chuyện nhỏ đó làm kế toán mất ngủ.

Một phần ba và không phẩy một

Hãy thử viết một phần ba dưới dạng số thập phân. Bạn được không phẩy ba ba ba, và cứ thế mãi không hết. Nếu chỉ được phép viết mười chữ số, bạn phải cắt đi, và con số bạn viết ra sẽ nhỏ hơn một phần ba một chút xíu. Nhân nó với ba, bạn được không phẩy chín chín chín, không phải một.

Máy tính gặp đúng vấn đề đó, chỉ là ở những con số khác. Trong hệ nhị phân, những số như không phẩy năm, không phẩy hai lăm viết gọn được. Nhưng không phẩy một thì trở thành một chuỗi lặp mãi không hết. Máy phải cắt ở một độ dài nhất định, và thế là con số được lưu chỉ gần bằng không phẩy một.

Mỗi lần tính, sai lệch cực nhỏ đó có thể cộng dồn. Một phép tính thì không ai thấy. Nhưng cộng một triệu lần, hoặc so sánh hai số xem có bằng nhau không, thì vấn đề lộ ra.

Cách máy lưu số thập phân kiểu này gọi là dấu phẩy động. Nó rất tốt cho khoa học, đồ hoạ, nơi cần phạm vi số rất rộng và chấp nhận sai số nhỏ. Nó chỉ không hợp với những nơi cần đúng đến từng đồng.

Khi một đồng lệch trở thành chuyện lớn

Hãy tưởng tượng phần mềm bán hàng tính giá sau giảm giá, cộng thuế, cộng phí giao hàng, rồi chia cho nhiều món. Mỗi bước có thể sinh một chút lệch. Cuối cùng tổng hoá đơn lệch một đồng so với tổng các dòng cộng lại.

Một đồng thì chẳng ai quan tâm, bạn nghĩ vậy. Cho tới khi kế toán đối chiếu cuối tháng. Sổ sách không khớp dù chỉ một đồng nghĩa là phải đi tìm vì sao, và tìm một đồng lệch trong hàng nghìn hoá đơn có thể mất cả ngày. Chị kế toán sẽ không tin phần mềm nữa.

Tệ hơn là khi so sánh. Chương trình kiểm tra khách đã trả đủ tiền chưa bằng cách so số tiền trả với số tiền phải trả. Nếu một bên là con số tính ra có lệch cực nhỏ, phép so sánh bằng nhau sẽ trả về sai, và khách đã trả đủ vẫn bị báo thiếu.

Đây là loại lỗi rất khó thấy khi thử nghiệm, vì với phần lớn con số, kết quả hiện ra trông vẫn đúng. Nó chỉ xuất hiện với vài tổ hợp số nhất định.

Lưu tiền bằng số nguyên

Cách phổ biến nhất mà người làm phần mềm tài chính dùng là không lưu tiền bằng số thập phân kiểu dấu phẩy động. Họ lưu bằng số nguyên theo đơn vị nhỏ nhất. Với tiền đồng Việt Nam, đơn vị nhỏ nhất thường dùng chính là đồng, và tiền đồng hầu như không có phần lẻ, nên việc này khá tự nhiên.

Với ngoại tệ có phần xu, người ta lưu theo xu. Mười đô la năm mươi xu được lưu là một nghìn không trăm năm mươi xu. Số nguyên thì máy cộng trừ chính xác tuyệt đối, không có sai lệch.

Cách thứ hai là dùng kiểu số thập phân chính xác mà nhiều ngôn ngữ và cơ sở dữ liệu cung cấp sẵn. Kiểu này lưu số theo hệ thập phân như con người, chậm hơn một chút nhưng đúng từng chữ số.

Điều cần tránh nhất là trộn lẫn: chỗ này lưu số nguyên, chỗ kia tính bằng dấu phẩy động, chỗ nọ lưu kiểu chính xác. Khi dữ liệu chạy qua lại giữa các chỗ, lỗi sẽ chui vào đúng những khe đó.

Làm tròn cũng là một quyết định

Dù lưu chính xác, bạn vẫn phải làm tròn. Giảm giá mười lăm phần trăm cho món hàng giá lẻ sẽ ra số có phần lẻ. Tỷ giá ngoại tệ có nhiều chữ số sau dấu phẩy. Thuế tính trên từng dòng hay trên tổng hoá đơn cũng cho ra kết quả khác nhau.

Làm tròn lên, làm tròn xuống, hay làm tròn về gần nhất? Làm tròn ở từng dòng hay chỉ ở tổng? Mỗi cách đều hợp lý, nhưng chúng cho ra con số khác nhau. Và một khi đã chọn, cả hệ thống phải dùng thống nhất một cách.

Đây không phải chuyện lập trình viên tự quyết. Nó là quy tắc nghiệp vụ, cần hỏi kế toán, hỏi người hiểu quy định về hoá đơn. Người lập trình giỏi biết đặt câu hỏi này sớm, trước khi có hàng nghìn hoá đơn đã tính theo một cách chưa ai đồng ý.

Mình hay thấy những đội phải sửa lại toàn bộ cách tính giá sau khi phần mềm đã chạy, chỉ vì không ai hỏi câu làm tròn thế nào ngay từ đầu.

So sánh số thập phân sao cho đúng

Ngoài chuyện tiền, những lúc bạn buộc phải dùng số dấu phẩy động, như đo khoảng cách, tính diện tích, xử lý dữ liệu cảm biến, thì đừng bao giờ so sánh bằng nhau tuyệt đối. Thay vào đó, hãy hỏi hai số có đủ gần nhau không, chênh lệch có nhỏ hơn một ngưỡng chấp nhận được không.

Ngưỡng đó bao nhiêu tuỳ bài toán. Đo đất tính bằng mét thì lệch một phần nghìn mét không sao. Tính quỹ đạo thì khác. Chọn ngưỡng cũng là hiểu nghiệp vụ.

Và khi hiển thị cho người dùng, hãy định dạng số với số chữ số hợp lý. Người dùng không cần thấy mười sáu chữ số sau dấu phẩy. Hiện ra như vậy họ chỉ nghĩ phần mềm bị lỗi.

Bài học lớn hơn ở đây là máy tính không thông minh như ta tưởng. Nó làm đúng những gì nó được thiết kế để làm, và người lập trình phải hiểu thiết kế đó để không bắt nó làm điều nó không làm được.

Câu hỏi thường gặp

Vì sao không phẩy một cộng không phẩy hai không bằng không phẩy ba trong lập trình?

Vì cả hai số đều không thể lưu chính xác trong hệ nhị phân mà chỉ được lưu gần đúng. Tổng của hai số gần đúng lệch một chút so với cách máy lưu không phẩy ba, nên phép so sánh bằng nhau trả về sai.

Có phải máy tính tính toán sai không?

Không hẳn. Máy làm đúng theo cách lưu số mà nó được thiết kế. Sai lệch nằm ở chỗ một số con số không biểu diễn được chính xác, giống như một phần ba không viết hết được bằng số thập phân.

Phần mềm kế toán lưu tiền thế nào cho chính xác?

Thường lưu bằng số nguyên theo đơn vị nhỏ nhất hoặc dùng kiểu số thập phân chính xác. Cả hai cách đều tránh được sai lệch cộng dồn của số dấu phẩy động.

Nên làm tròn tiền ở từng dòng hay ở tổng hoá đơn?

Đây là quy tắc nghiệp vụ, cần thống nhất với kế toán và quy định về hoá đơn. Điều quan trọng là chọn một cách và dùng nhất quán trên toàn hệ thống.

Số dấu phẩy động dùng vào việc gì là hợp?

Hợp với khoa học, đồ hoạ, đo đạc, xử lý tín hiệu, nơi cần phạm vi số rất rộng và chấp nhận sai số rất nhỏ. Không hợp với tiền bạc và những con số cần khớp đến từng đơn vị.

Cần tư vấn cụ thể cho trường hợp của bạn?

Chúng tôi sẽ liên hệ trong vòng 24 giờ.

Đăng ký tư vấn ngay

Bài viết liên quan

Bạn cần hỗ trợ thêm?

Để lại thông tin, chúng tôi sẽ liên hệ trong 24 giờ.