2.39. Làm việc với số thực

Số thực dấu phẩy động trông giống như số thập phân thông thường, nhưng một vài hành vi của chúng gây bất ngờ cho những người chưa từng gặp chúng trước -- và một trong những hành vi đó rõ ràng hơn trên MicroPython so với Python trên máy tính để bàn. Trang này đề cập đến những gì cần mong đợi và cách viết mã float không bị lỗi âm thầm.

2.39.1. Độ chính xác

Python float là một số dấu phẩy động nhị phân IEEE 754. Trên hầu hết các bản dựng MicroPython, đây là độ chính xác đơn (32-bit), trong khi CPython trên máy tính để bàn sử dụng độ chính xác kép (64-bit). Độ chính xác đơn mang khoảng bảy chữ số thập phân; kép mang khoảng mười lăm.

>>> 0.1 + 0.2
0.3000000

>>> 1.0 / 3.0
0.3333333

>>> 1e30 * 1e30
inf

Phạm vi có thể biểu diễn cũng hẹp hơn ở cả hai đầu: các số lớn hơn khoảng 3.4e38 về độ lớn sẽ tràn sang inf, và các số nhỏ hơn khoảng 1.2e-38 sẽ làm tròn về không.

2.39.2. So sánh số thực

Cạm bẫy phổ biến nhất là kiểm tra bằng nhau với ==:

>>> 0.1 + 0.2 == 0.3
False

Cả hai biểu thức trông như phải bằng nhau, nhưng kết quả của 0.1 + 0.2 là giá trị có thể biểu diễn gần nhất, không bằng chính xác 0.3. Thay vào đó hãy dùng kiểm tra dung sai -- hỏi xem hai số thực có đủ gần nhau thay vì giống hệt nhau không:

if abs(a - b) < 1e-6:
    # close enough
    ...

Việc chọn dung sai phụ thuộc vào quy mô của các giá trị. Một giá trị cố định 1e-6 hoạt động tốt khi các số ở khoảng bậc 1; dung sai tương đối tốt hơn khi các giá trị thay đổi theo bậc độ lớn.

math.isclose() xử lý cả hai cùng một lúc:

from math import isclose

isclose(0.1 + 0.2, 0.3)         # True
isclose(1.0e6 + 1, 1.0e6)       # True (within default tolerance)

Hai đối số từ khóa kiểm soát loại "gần" nào được tính:

  • rel_tol -- dung sai tương đối, mặc định 1e-9. Hai giá trị khớp nếu hiệu của chúng nằm trong phần này của giá trị lớn hơn. Tốt cho các so sánh chung trên bất kỳ quy mô nào.

  • abs_tol -- dung sai tuyệt đối, mặc định 0. Hai giá trị khớp nếu hiệu của chúng nằm trong lượng cố định này.

math.isclose() trả về True nếu một trong hai dung sai được đáp ứng. Các giá trị mặc định ổn với hầu hết các cặp số khác không; cạm bẫy là khi một trong các giá trị có thể bằng chính xác không. Kiểm tra dung sai tương đối tính ra thành "hiệu ≤ rel_tol × giá trị lớn nhất", và giá trị lớn nhất là không, nên kiểm tra luôn thất bại:

>>> isclose(0.0, 1e-12)
False

Kiểm tra dung sai tuyệt đối không có vấn đề như vậy -- truyền abs_tol khi không là một giá trị bạn có thể so sánh với:

>>> isclose(0.0, 1e-12, abs_tol=1e-9)
True

2.39.3. Sai số tích lũy

Các tổng dài của số thực mất độ chính xác nhanh hơn trên MicroPython so với CPython, vì mỗi kết quả trung gian được làm tròn về độ chính xác 32-bit:

total = 0.0
for _ in range(1000000):
    total += 0.1

print(total)        # noticeably off from 100000.0

Đối với các phép cộng lặp lại nơi độ chính xác quan trọng, hai mẫu giúp ích:

  • Tích lũy vào số nguyên bất cứ khi nào các giá trị có thể được chia tỷ lệ thành số nguyên -- làm việc bằng mili giây thay vì giây, hoặc mili volt thay vì volt, sau đó chuyển đổi một lần ở cuối.

  • Tính toán theo từng lô nhỏ hơn và tổng hợp kết quả lô, sao cho mỗi phép cộng là giữa các giá trị có độ lớn tương tự.

Phía số nguyên không có giới hạn như vậy -- MicroPython integers có độ chính xác tùy ý, giống như CPython. Khi bạn có sự lựa chọn, hãy ưu tiên số học nguyên cho bất cứ điều gì mà việc mất độ chính xác sẽ tích lũy.