2.39. การทำงานกับทศนิยม

ตัวเลขทศนิยมดูเหมือนเลขทศนิยมธรรมดา แต่พฤติกรรมบางอย่างของพวกมันทำให้ผู้อ่านที่ยังไม่เคยพบเจอรู้สึกแปลกใจ -- และพฤติกรรมหนึ่งในนั้นเด่นชัดกว่าบน MicroPython มากกว่าบน Python สำหรับเดสก์ท็อป หน้านี้ครอบคลุมสิ่งที่ควรคาดหวังและวิธีเขียนโค้ด float ที่ไม่ทำงานผิดพลาดอย่างเงียบๆ

2.39.1. ความแม่นยำ

float ของ Python คือตัวเลขทศนิยมไบนารี IEEE 754 บน MicroPython ส่วนใหญ่จะเป็น single precision (32-bit) ในขณะที่ CPython บนเดสก์ท็อปใช้ double precision (64-bit) Single precision รองรับความแม่นยำประมาณเจ็ดหลักทศนิยม; double รองรับประมาณสิบห้าหลัก

>>> 0.1 + 0.2
0.3000000

>>> 1.0 / 3.0
0.3333333

>>> 1e30 * 1e30
inf

ช่วงที่แทนได้ยังแคบลงที่ทั้งสองปลาย: ตัวเลขที่มีขนาดใหญ่กว่าประมาณ 3.4e38 ล้นเป็น inf และตัวเลขที่เล็กกว่าประมาณ 1.2e-38 จะถูกปัดเป็นศูนย์

2.39.2. การเปรียบเทียบทศนิยม

ข้อผิดพลาดที่พบบ่อยที่สุดคือการทดสอบความเท่าเทียมกันด้วย ==:

>>> 0.1 + 0.2 == 0.3
False

ทั้งสองนิพจน์ดูเหมือนควรจะเท่ากัน แต่ผลลัพธ์ของ 0.1 + 0.2 คือค่าที่แทนได้ใกล้เคียงที่สุด ซึ่งไม่ใช่ 0.3 แน่นอน ให้ใช้ การตรวจสอบแบบ tolerance แทน -- ถามว่า float สองตัวใกล้เคียงพอหรือไม่ แทนที่จะเหมือนกันทั้งหมด:

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

การเลือก tolerance ขึ้นอยู่กับขนาดของค่า tolerance แบบ 1e-6 คงที่ทำงานได้ดีเมื่อตัวเลขอยู่ในลำดับที่ 1; tolerance แบบสัมพัทธ์ ดีกว่าเมื่อค่าแตกต่างกันหลายลำดับของขนาด

math.isclose() จัดการทั้งสองพร้อมกัน:

from math import isclose

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

อาร์กิวเมนต์คีย์เวิร์ดสองตัวควบคุมว่า "ใกล้" แบบไหนที่นับ:

  • rel_tol -- tolerance แบบสัมพัทธ์ ค่าเริ่มต้น 1e-9 ค่าสองค่าตรงกันหากผลต่างอยู่ภายในเศษส่วนนี้ของค่าที่ใหญ่กว่า เหมาะสำหรับการเปรียบเทียบทั่วไปในทุกขนาด

  • abs_tol -- tolerance แบบสัมบูรณ์ ค่าเริ่มต้น 0 ค่าสองค่าตรงกันหากผลต่างอยู่ภายในปริมาณคงที่นี้

math.isclose() คืน True หาก tolerance ใดๆ ถูกตอบสนอง ค่าเริ่มต้นดีสำหรับคู่ตัวเลขที่ไม่เป็นศูนย์ส่วนใหญ่; กับดักอยู่ที่เมื่อค่าใดค่าหนึ่งอาจเป็นศูนย์แน่นอน การตรวจสอบ relative-tolerance ให้ผลลัพธ์เป็น "ผลต่าง ≤ rel_tol × ค่าที่ใหญ่กว่า" และค่าที่ใหญ่กว่าเป็นศูนย์ ดังนั้นการตรวจสอบล้มเหลวเสมอ:

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

การตรวจสอบ absolute-tolerance ไม่มีปัญหาดังกล่าว -- ส่ง abs_tol เมื่อใดก็ตามที่ศูนย์เป็นค่าที่คุณอาจเปรียบเทียบกัน:

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

2.39.3. การสะสมความคลาดเคลื่อน

ผลรวมของทศนิยมที่ยาวจะสูญเสียความแม่นยำเร็วกว่าบน MicroPython มากกว่าบน CPython เพราะผลลัพธ์ระหว่างกลางแต่ละตัวจะถูกปัดกลับเป็น 32-bit precision:

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

print(total)        # noticeably off from 100000.0

สำหรับการบวกซ้ำๆ ที่ความแม่นยำมีความสำคัญ มีสองแนวทางที่ช่วยได้:

  • สะสมเป็นจำนวนเต็ม เมื่อใดก็ตามที่ค่าสามารถปรับเป็นจำนวนเต็ม -- ทำงานเป็นมิลลิวินาทีแทนวินาที หรือมิลลิโวลต์แทนโวลต์ จากนั้นแปลงครั้งเดียวในตอนท้าย

  • คำนวณเป็นแบทช์เล็กๆ และรวมผลลัพธ์ของแบทช์ ดังนั้นการบวกแต่ละครั้งจะอยู่ระหว่างค่าที่มีขนาดใกล้เคียงกัน

ฝั่งจำนวนเต็มไม่มีขีดจำกัดดังกล่าว -- จำนวนเต็มใน MicroPython เป็น arbitrary precision เช่นเดียวกับ CPython เมื่อคุณมีทางเลือก ให้เลือกใช้เลขคณิตจำนวนเต็มสำหรับทุกอย่างที่การสูญเสียความแม่นยำอาจสะสม