0.1 + 0.2 không bằng 0.3
Hiện tượng lạ
Phần tiêu đề “Hiện tượng lạ”# Phép toán đơn giảnresult = 0.1 + 0.2print(result) # 0.30000000000000004print(result == 0.3) # False
# Các ví dụ khácprint(0.1 + 0.1 + 0.1) # 0.30000000000000004print(0.1 + 0.1 + 0.1 == 0.3) # False
print(1.2 - 1.0) # 0.19999999999999996print(1.2 - 1.0 == 0.2) # FalseWhat the Python?! Tại sao 0.1 + 0.2 không bằng 0.3?! Toán học bị phá vỡ rồi sao?! 😱
Giải thích
Phần tiêu đề “Giải thích”Floating-point representation
Phần tiêu đề “Floating-point representation”Vấn đề KHÔNG phải lỗi của Python, mà là cách máy tính lưu trữ số thập phân theo IEEE 754 floating-point standard.
# Máy tính lưu số dưới dạng binary (nhị phân)# Một số thập phân KHÔNG thể biểu diễn chính xác trong binary
# Ví dụ: 0.1 trong binary là số vô hạn tuần hoàn# 0.1 (decimal) = 0.0001100110011001100110011... (binary, vô hạn)
# Máy tính phải làm tròn → Mất độ chính xácXem representation thực tế
Phần tiêu đề “Xem representation thực tế”# Xem giá trị thực sự được lưuprint(f"{0.1:.20f}") # 0.10000000000000000555print(f"{0.2:.20f}") # 0.20000000000000001110print(f"{0.3:.20f}") # 0.29999999999999998890
# Khi cộng lạiprint(f"{0.1 + 0.2:.20f}") # 0.30000000000000004441Tại sao lại như vậy?
Phần tiêu đề “Tại sao lại như vậy?”Binary không biểu diễn được mọi decimal
Phần tiêu đề “Binary không biểu diễn được mọi decimal”# Giống như:# - Decimal không biểu diễn được 1/3 chính xác# 1/3 = 0.333333... (vô hạn)
# - Binary không biểu diễn được 0.1 chính xác# 0.1 (decimal) = 0.00011001100110011... (binary, vô hạn)
# Ví dụ decimal:print(1 / 3) # 0.3333333333333333print(1 / 3 * 3) # 1.0 (may be)print(1 / 3 + 1 / 3 + 1 / 3) # 0.9999999999999999 or 1.0Một số decimal → binary chính xác, một số không
Phần tiêu đề “Một số decimal → binary chính xác, một số không”# ✅ Chính xác (powers of 2)print(0.5) # 0.5 (= 1/2 = 2^-1)print(0.25) # 0.25 (= 1/4 = 2^-2)print(0.125) # 0.125 (= 1/8 = 2^-3)
print(0.5 + 0.5 == 1.0) # Trueprint(0.25 + 0.25 == 0.5) # True
# ❌ Không chính xácprint(0.1) # 0.1 (không biểu diễn được chính xác)print(0.2) # 0.2 (không biểu diễn được chính xác)print(0.3) # 0.3 (không biểu diễn được chính xác)
print(0.1 + 0.2 == 0.3) # FalseCác vấn đề thực tế
Phần tiêu đề “Các vấn đề thực tế”Vấn đề 1: So sánh bằng
Phần tiêu đề “Vấn đề 1: So sánh bằng”# SAI - So sánh trực tiếpprice = 0.1 + 0.2if price == 0.3: # False! print("Đúng giá")else: print("Sai giá") # In ra dòng này!Vấn đề 2: Accumulation errors
Phần tiêu đề “Vấn đề 2: Accumulation errors”# Lỗi tích lũy khi cộng nhiều lầntotal = 0.0for _ in range(10): total += 0.1
print(total) # 0.9999999999999999print(total == 1.0) # False!Vấn đề 3: Financial calculations
Phần tiêu đề “Vấn đề 3: Financial calculations”# Tính tiền - RẤT NGUY HIỂM!price = 0.1quantity = 3total = price * quantity
print(total) # 0.30000000000000004print(total == 0.3) # False
# Nếu làm tròn sai → Mất tiền!Giải pháp
Phần tiêu đề “Giải pháp”1. So sánh với epsilon (tolerance)
Phần tiêu đề “1. So sánh với epsilon (tolerance)”# Dùng tolerance thay vì so sánh trực tiếpdef isclose(a, b, tolerance=1e-9): return abs(a - b) < tolerance
# Sử dụngresult = 0.1 + 0.2print(isclose(result, 0.3)) # True ✅
# Hoặc dùng math.isclose (Python 3.5+)import math
print(math.isclose(0.1 + 0.2, 0.3)) # True ✅print(math.isclose(0.1 + 0.2, 0.3, rel_tol=1e-9)) # True2. Dùng Decimal module
Phần tiêu đề “2. Dùng Decimal module”from decimal import Decimal
# Dùng Decimal cho độ chính xác caoa = Decimal('0.1') # QUAN TRỌNG: Dùng string, không phải float!b = Decimal('0.2')c = a + b
print(c) # 0.3print(c == Decimal('0.3')) # True ✅
# SAI - Dùng floatwrong = Decimal(0.1) # Decimal('0.1000000000000000055511151231257827021181583404541015625')print(wrong) # Vẫn không chính xác!
# ĐÚNG - Dùng stringcorrect = Decimal('0.1')print(correct) # 0.1 ✅3. Dùng fractions module
Phần tiêu đề “3. Dùng fractions module”from fractions import Fraction
# Dùng phân sốa = Fraction(1, 10) # 1/10b = Fraction(2, 10) # 2/10c = a + b
print(c) # 3/10print(c == Fraction(3, 10)) # True ✅print(float(c)) # 0.34. Làm tròn kết quả
Phần tiêu đề “4. Làm tròn kết quả”# Làm tròn đến số chữ số mong muốnresult = round(0.1 + 0.2, 2)print(result) # 0.3print(result == 0.3) # True ✅
# Cẩn thận - round cũng có quirksprint(round(2.5)) # 2 (banker's rounding)print(round(3.5)) # 45. Nhân với integer trước
Phần tiêu đề “5. Nhân với integer trước”# Trick: Nhân với 10, 100, ... để biến thành int# SAIprice_per_item = 0.1quantity = 3total = price_per_item * quantity # 0.30000000000000004
# ĐÚNG - Dùng centsprice_per_item_cents = 10 # 10 centsquantity = 3total_cents = price_per_item_cents * quantity # 30total_dollars = total_cents / 100 # 0.3Use cases cho từng giải pháp
Phần tiêu đề “Use cases cho từng giải pháp”math.isclose() - Cho tính toán khoa học
Phần tiêu đề “math.isclose() - Cho tính toán khoa học”import math
# Tính toán khoa học, physicsdef calculate_velocity(distance, time): velocity = distance / time expected = 299792.458 # km/s
if math.isclose(velocity, expected, rel_tol=1e-9): return "Đúng tốc độ ánh sáng!"Decimal - Cho financial calculations
Phần tiêu đề “Decimal - Cho financial calculations”from decimal import Decimal, ROUND_HALF_UP
# Tính toán tài chínhdef calculate_total(items): total = Decimal('0') for item in items: price = Decimal(str(item['price'])) quantity = Decimal(str(item['quantity'])) total += price * quantity
# Làm tròn 2 chữ số return total.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
items = [ {'price': 0.1, 'quantity': 3}, {'price': 0.2, 'quantity': 2},]print(calculate_total(items)) # 0.70 ✅Fraction - Cho toán học chính xác
Phần tiêu đề “Fraction - Cho toán học chính xác”from fractions import Fraction
# Phân số chính xácdef solve_equation(): # x = 1/3 + 1/6 x = Fraction(1, 3) + Fraction(1, 6) print(x) # 1/2 ✅
# So sánh chính xác if x == Fraction(1, 2): print("Đúng!")Common mistakes
Phần tiêu đề “Common mistakes”Mistake 1: So sánh trực tiếp
Phần tiêu đề “Mistake 1: So sánh trực tiếp”# SAIif 0.1 + 0.2 == 0.3: # ❌ pass
# ĐÚNGimport mathif math.isclose(0.1 + 0.2, 0.3): # ✅ passMistake 2: Dùng float cho money
Phần tiêu đề “Mistake 2: Dùng float cho money”# SAIbalance = 100.0balance -= 0.1balance -= 0.1balance -= 0.1print(balance == 99.7) # ❌ False
# ĐÚNGfrom decimal import Decimalbalance = Decimal('100.0')balance -= Decimal('0.1')balance -= Decimal('0.1')balance -= Decimal('0.1')print(balance == Decimal('99.7')) # ✅ TrueMistake 3: Accumulation trong loop
Phần tiêu đề “Mistake 3: Accumulation trong loop”# SAItotal = 0.0for i in range(100): total += 0.01print(total == 1.0) # ❌ Falseprint(total) # 0.9999999999999999
# ĐÚNGfrom decimal import Decimaltotal = Decimal('0')for i in range(100): total += Decimal('0.01')print(total == Decimal('1.0')) # ✅ TrueTesting với float
Phần tiêu đề “Testing với float”import math
def test_calculation(): result = complex_calculation() expected = 123.456
# SAI assert result == expected # ❌ Có thể fail
# ĐÚNG assert math.isclose(result, expected, rel_tol=1e-9) # ✅
# Hoặc với pytest # pytest.approx()Khi nào dùng float OK?
Phần tiêu đề “Khi nào dùng float OK?”✅ OK với float
Phần tiêu đề “✅ OK với float”# 1. Graphics, game development (không cần chính xác tuyệt đối)position_x = 10.5position_y = 20.3
# 2. Measurements (đã có sai số từ sensor)temperature = 25.6humidity = 60.2
# 3. Tính toán gần đúngaverage = sum(values) / len(values)
# 4. So sánh với epsilonif math.isclose(result, expected): pass❌ KHÔNG dùng float
Phần tiêu đề “❌ KHÔNG dùng float”# 1. Money, financeprice = Decimal('19.99') # ✅ Dùng Decimal
# 2. Exact fractionsratio = Fraction(1, 3) # ✅ Dùng Fraction
# 3. Counters, indicescount = 0 # ✅ Dùng intBest Practices
Phần tiêu đề “Best Practices”# 1. Luôn dùng math.isclose cho so sánh floatimport math
if math.isclose(a, b, rel_tol=1e-9): pass
# 2. Dùng Decimal cho tiềnfrom decimal import Decimal
price = Decimal('19.99')
# 3. Dùng string cho Decimalcorrect = Decimal('0.1') # ✅wrong = Decimal(0.1) # ❌
# 4. Round kết quả displaydisplay_value = round(0.1 + 0.2, 2) # 0.3
# 5. Document tolerance trong testsassert math.isclose(result, expected, rel_tol=1e-6) # Tolerance rõ ràngTóm tắt
Phần tiêu đề “Tóm tắt”| Vấn đề | Giải pháp | Use case |
|---|---|---|
0.1 + 0.2 != 0.3 |
math.isclose() |
Tính toán khoa học |
| Money calculations | Decimal |
Finance |
| Exact fractions | Fraction |
Toán học |
| Display rounding | round() |
UI output |
| Accumulation errors | Decimal hoặc int |
Loops, totals |
Ghi nhớ
Phần tiêu đề “Ghi nhớ”Float không chính xác tuyệt đối!
- ❌ KHÔNG so sánh float với
==- ✅ Dùng
math.isclose()để so sánh- 💰 Dùng
Decimalcho money- 🔢 Dùng
Fractioncho phân số chính xác- 📊 Dùng
round()cho display- ⚠️ Đây KHÔNG phải bug Python, mà là cách máy tính hoạt động!
Golden Rule: Never use == with floats, always use tolerance-based comparison!