Câu 1Lựa chọn phù hợp nhất khi bạn không thể dung thứ dù chỉ một xu lệch trong tính toán tiền tệ?
decimal và fractions — Số học chính xác không sai số float
Học module decimal và fractions của Python từ căn bản. Bao quát vấn đề 0.1 + 0.2 != 0.3 với float, tính tiền chính xác với Decimal (và lý do tạo Decimal từ chuỗi), số học với Fraction tự động rút gọn, và chọn giữa ba kiểu số — kèm bài tập thực hành chạy được.
Bài này bao quát hai module cho các trường hợp `float` gây vấn đề. decimal.Decimal thực hiện số học base-10 chính xác, khiến nó thiết yếu cho tính toán tiền tệ; fractions.Fraction giữ tử số và mẫu số dưới dạng số nguyên để tính với phân số rút gọn một cách chính xác. Cả hai tồn tại để loại bỏ các sai số làm tròn không thể tránh khỏi của float.
Sai số làm tròn của float và decimal.Decimal — Đừng dùng float cho phép toán chính xác
float của Python được biểu diễn nội bộ ở dạng nhị phân, nên các phân số thập phân như `0.1` không chia hết trong nhị phân mang một sai số làm tròn nhỏ. Ví dụ nổi tiếng nhất là `0.1 + 0.2 == 0.3` trả về `False`. Với đồ họa hay tính toán khoa học sai số có thể bỏ qua, nhưng với tổng tài chính dù lệch nhỏ cũng trở thành nghiêm trọng.
Đó là lý do decimal.Decimal tồn tại. Nó được biểu diễn nội bộ ở base-10, nên tạo Decimal từ chuỗi "0.1" cho phép bạn tính toán không có sai số.
0.1. Decimal dùng biểu diễn nội bộ base-10, nên tính toán đúng như chuỗi đầu vào, nhưng chậm hơn float. Khi xử lý tiền và thuế, hãy chọn Decimal.from decimal import Decimal
# sai số làm tròn của float
print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False ← trái với trực giác!
# Tạo Decimal từ chuỗi (tạo từ float sẽ kế thừa sai số)
a = Decimal("0.1")
b = Decimal("0.2")
print(a + b) # 0.3
print(a + b == Decimal("0.3")) # True
# Ví dụ tính tiền
price = Decimal("1980")
tax_rate = Decimal("0.10")
total = price * (Decimal("1") + tax_rate)
print(total) # 2178.00
Tạo Decimal từ chuỗi, không phải từ float
Nếu bạn truyền float — Decimal(0.1) — bạn đang đưa vào một giá trị đã mang sai số làm tròn, nên bạn kết thúc với một Decimal vẫn có sai số của float. Luôn truyền chuỗi: Decimal("0.1"). Khó phát hiện trong test và là cái bẫy kinh điển khiến lệch một xu trong production lần đầu tiên.
fractions.Fraction — Giữ phân số là phân số
Fraction là một kiểu lưu tử số và mẫu số dưới dạng số nguyên cho số học. Tạo một cái với Fraction(1, 3), và các phép cộng, trừ, nhân tiếp theo trả về kết quả phân số rút gọn. Ở float, 1/3 là xấp xỉ 0.3333333333333333, nhưng với Fraction nó vẫn là "một phần ba" chính nó.
+ / - / * cũng là Fraction, và chúng được rút gọn tự động. Bạn có thể chuyển sang float với float(f), nhưng điều đó gây sai số làm tròn.| Kiểu | Biểu diễn nội bộ | Phù hợp nhất với |
|---|---|---|
| int | Số nguyên | Số đếm và đếm số (không sai số) |
| float | Dấu phẩy động nhị phân | Tính toán khoa học, đồ họa (nhanh, có sai số) |
| Decimal | Biểu diễn base-10 | Tiền và thuế (khi cần độ chính xác base-10) |
| Fraction | Tử số và mẫu số (số nguyên) | Tỉ lệ và xác suất (khi muốn dạng rút gọn) |
Đối tượng Fraction phơi bày các thuộc tính cho tử số và mẫu số. f.numerator trả về tử số và f.denominator trả về mẫu số — cả hai đều sau khi rút gọn. Ví dụ, Fraction(2, 6) được chuẩn hóa nội bộ thành Fraction(1, 3), nên .numerator là 1 và .denominator là 3.
| Thuộc tính / Phương thức | Ý nghĩa | Ví dụ |
|---|---|---|
| f.numerator | Tử số | Fraction(1, 3).numerator → 1 |
| f.denominator | Mẫu số | Fraction(1, 3).denominator → 3 |
| float(f) | Chuyển sang float | float(Fraction(1, 2)) → 0.5 |
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
Câu 2Khi tạo một Decimal, cách đúng không gây sai số là gì?
Câu 3Bạn dùng cái nào khi muốn tính tỉ lệ hoặc xác suất dưới dạng phân số rút gọn?