2.39. Робота з числами з рухомою комою¶
Числа з рухомою комою виглядають як звичайні десяткові числа, але деякі їхні властивості дивують читачів, які з ними раніше не стикалися – і одна з цих властивостей більш виражена в MicroPython, ніж у настільному Python. Ця сторінка описує, чого очікувати і як писати код із числами з рухомою комою, що не матиме прихованих збоїв.
2.39.1. Точність¶
Python’s float – це число з рухомою комою IEEE 754. У більшості збірок MicroPython воно має одинарну точність (32-біт), тоді як настільний CPython використовує подвійну точність (64-біт). Одинарна точність зберігає приблизно сім значущих десяткових цифр; подвійна – близько п’ятнадцяти.
>>> 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. Натомість використовуйте перевірку з допуском – питайте, чи два числа достатньо близькі, а не ідентичні:
if abs(a - b) < 1e-6:
# close enough
...
Вибір допуску залежить від масштабу значень. Фіксований 1e-6 добре підходить, коли числа мають порядок 1; відносний допуск краще підходить, коли значення відрізняються на кілька порядків.
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– відносний допуск, за замовчуванням1e-9. Два значення відповідають, якщо їхня різниця не перевищує цю частку від більшого з них. Добре підходить для загальних порівнянь у будь-якому масштабі.abs_tol– абсолютний допуск, за замовчуванням0. Два значення відповідають, якщо їхня різниця не перевищує цю фіксовану величину.
math.isclose() повертає True, якщо виконується хоча б один допуск. Значення за замовчуванням підходять для більшості пар ненульових чисел; пастка виникає, коли одне зі значень може бути рівно нулем. Перевірка відносного допуску зводиться до «різниця ≤ rel_tol × найбільше значення», а найбільше значення – нуль, тому перевірка завжди провалюється:
>>> isclose(0.0, 1e-12)
False
Перевірка абсолютного допуску не має такої проблеми – передавайте abs_tol, якщо нуль є значенням, з яким ви можете порівнювати:
>>> isclose(0.0, 1e-12, abs_tol=1e-9)
True
2.39.3. Накопичувальне відхилення¶
Довгі суми чисел з рухомою комою втрачають точність швидше в MicroPython, ніж у CPython, оскільки кожен проміжний результат округлюється до 32-бітної точності:
total = 0.0
for _ in range(1000000):
total += 0.1
print(total) # noticeably off from 100000.0
Для повторних додавань, де важлива точність, допомагають два підходи:
Накопичуйте в цілих числах, коли значення можна масштабувати до цілих – працюйте в мілісекундах замість секунд або в міліВольтах замість Вольт, а потім перетворіть один раз наприкінці.
Обчислюйте меншими пакетами і підсумовуйте результати пакетів, щоб кожне додавання відбувалося між значеннями подібного масштабу.
Цілочисельна сторона не має таких обмежень – цілі числа MicroPython мають довільну точність, як і в CPython. Там, де є вибір, надавайте перевагу цілочисельній арифметиці для всього, де втрата точності може накопичуватися.