Học bằng cách đọc theo thứ 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ố.

float so với Decimal
float0.1 + 0.2→ 0.30000000000000004sai số làm trònDecimalDecimal('0.1') + Decimal('0.2')→ Decimal('0.3')không sai số
float nhanh (dùng lệnh CPU) nhưng gây sai số làm tròn với các giá trị như 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.

Trước tiên quan sát sai số làm tròn của float, sau đó chuyển sang Decimal cho phép tính bao gồm thuế chính xác.

① Import Decimal từ module decimal.

② Tính 0.1 + 0.2 với float và in ra dạng float: ◯◯ (bạn sẽ thấy sai số).

③ In kết quả của 0.1 + 0.2 == 0.3 dạng float equal?: ◯◯ (trả về False, trái với trực giác).

④ Tính 0.1 + 0.2 với Decimal và in ra dạng Decimal: ◯◯ (lần này không có sai số).

⑤ Định nghĩa giá chưa thuế 1980 và thuế suất 10% dưới dạng Decimal, tính tổng có thuế và in ra dạng total: ◯◯.

Python Editor

Chạy code để xem đầu ra

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ó.

Tổng quan Fraction
Fraction(1, 3)+ Fraction(1, 6)Fraction(1, 2)(tự rút gọn)
Tạo bằng Fraction(tử số, mẫu số) — kiểu tỉ số nguyên. Kết quả của + / - / * 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ểuBiểu diễn nội bộPhù hợp nhất với
intSố nguyênSố đếm và đếm số (không sai số)
floatDấu phẩy động nhị phânTính toán khoa học, đồ họa (nhanh, có sai số)
DecimalBiểu diễn base-10Tiền và thuế (khi cần độ chính xác base-10)
FractionTử 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 .numerator1.denominator3.

Thuộc tính / Phương thứcÝ nghĩaVí dụ
f.numeratorTử sốFraction(1, 3).numerator → 1
f.denominatorMẫu sốFraction(1, 3).denominator → 3
float(f)Chuyển sang floatfloat(Fraction(1, 2)) → 0.5

Cộng hai phân số và truy cập các thuộc tính của kết quả. Tính 1/3 + 1/6 trong khi giữ nó là Fraction, và xác nhận đáp án được rút gọn tự động.

① Import Fraction từ module fractions.

② Tạo 1/31/6 dưới dạng Fraction.

③ Cộng hai cái và in kết quả dạng sum: ◯/◯ (sẽ được tự rút gọn).

④ Đọc tử sốmẫu số từ kết quả qua thuộc tính, và in ra dạng numerator: ◯ denominator: ◯.

⑤ Chuyển kết quả sang float và in ra dạng as float: ◯.◯.

Python Editor

Chạy code để xem đầu ra
QUIZ

Kiểm tra kiến thức

Hãy trả lời từng câu hỏi một.

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ệ?

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?