Три рівні. Кожен наступний спирається на попередній, але братися можна за будь-який.
Пиши в окремому файлі homework_04.py або в новому зошиті — головне, щоб код
запускався й друкував результат.
Дозволено з бібліотек: math, decimal, fractions, sys. Більше нічого не треба.
рівень 1Рівень 1 — База
1.1 · Розкладач часу
Напиши функцію розкласти_секунди(всього), яка перетворює кількість секунд
у кортеж (дні, години, хвилини, секунди). Використай тільки // і % —
жодних datetime.
Виведи результат для всього = 1_000_000 у форматі
1000000 с = 11 д 13 год 46 хв 40 с.
Зроблено, якщо: для будь-якого невід'ємного цілого виконується
assert дні * 86400 + години * 3600 + хвилини * 60 + секунди == всього
і всі три молодші компоненти лежать у своїх діапазонах
(години < 24, хвилини < 60, секунди < 60).
1.2 · Чек без копійчаних втрат
Дано список цін рядками: ["149.99", "3.55", "27.05", "0.07"].
Порахуй суму двома способами — через float і через Decimal із рядка —
і надрукуй обидві разом із точним десятковим значенням float-суми
(Decimal(float_сума)).
Зроблено, якщо: твій код друкує рядок, у якому видно, що Decimal-сума
дорівнює рівно 180.66, а float-сума — ні, і виконується
assert decimal_сума == Decimal("180.66")
assert float_сума != 180.66
Спробуй потім замінити список на
["149.99", "3.50", "27.05", "0.01"]— там float-сума випадково зійдеться рівно. Це і є найгірша властивість помилок із рухомою крапкою: вони не завжди себе показують.
рівень 2Рівень 2 — Плюс
2.1 · Чесний калькулятор здачі
Напиши функцію здача(сума_копійок, номінали), яка розкладає суму на найменшу
кількість купюр і монет. номінали — список у копійках, відсортований за спаданням,
наприклад [50000, 20000, 10000, 5000, 2000, 1000, 500, 200, 100, 50, 25, 10, 5, 2, 1].
Функція повертає список пар (номінал, скільки штук), пропускаючи нульові.
Уся арифметика — на цілих числах. Жодного float у розв'язку бути не повинно.
Зроблено, якщо: для здача(78_63, номінали) (тобто 78 грн 63 коп) сума
номінал * кількість по всіх парах точно дорівнює вихідній сумі, і це перевірено
через assert. Додатково: покажи, що для суми 10_00 результат складається
рівно з однієї позиції.
2.2 · Де ламається наївне порівняння
Досліди, наскільки далеко можна відійти від числа, щоб == іще казав True.
Візьми три опорні числа: 1.0, 1000.0 і 1e15. Для кожного знайди найменше
додатне крок, при якому x + крок != x.
Підказка: не перебирай — скористайся math.ulp(x) і перевір, що
x + math.ulp(x) != x, а x + math.ulp(x) / 2 == x.
Побудуй таблицю: опорне число, math.ulp, і у скільки разів крок більший,
ніж біля одиниці.
Зроблено, якщо: у таблиці видно, що крок росте пропорційно самому числу,
і ти пояснив словами (2-3 речення), чому біля 1e16 додавання одиниці
взагалі нічого не змінює. Перевір це твердження через
assert 1e16 + 1 == 1e16
рівень 3Рівень 3 — Виклик
3.1 · Свій розкладач float на біти
Напиши функцію розкласти(x), яка для додатного float повертає кортеж
(поле_порядку, мантиса) — два цілі числа з внутрішнього представлення,
не використовуючи struct, Decimal і .hex().
Дозволено: math.frexp, цілочисельна арифметика, зсуви.
Потім напиши зворотну функцію зібрати(поле_порядку, мантиса) і переконайся,
що вона повертає рівно вихідне число.
Зроблено, якщо: виконуються всі три перевірки
assert розкласти(0.1) == (1019, 2702159776422298)
assert розкласти(0.5) == (1022, 0)
for проба in [0.1, 0.5, 2.675, 1.0, 3.75, 1e16, 1e-7]:
assert зібрати(*розкласти(проба)) == проба
3.2 · Точне десяткове значення без Decimal
Спираючись на 3.1, напиши функцію точний_запис(x), яка повертає рядок
із точним десятковим значенням збереженого числа — усі цифри, без округлення.
Знову без Decimal: тільки цілі числа Python (вони ж необмежені!).
Підказка: якщо x = мантиса × 2^(-k) при k > 0, то помножити чисельник
на 5^k — і ти отримаєш ціле число, у якого треба лише поставити кому
за k знаків від кінця. Бо 2^(-k) = 5^k / 10^k.
Зроблено, якщо:
assert точний_запис(0.1) == "0.1000000000000000055511151231257827021181583404541015625"
assert точний_запис(0.5) == "0.5"
assert точний_запис(2.675) == "2.67499999999999982236431605997495353221893310546875"
Підказки
- Не бійся цілих чисел. У Рівні 3 всі проміжні значення — це
int, а він у Python необмежений.5 ** 1074обчислиться без жодних проблем. math.frexp(x)повертає пару(m, e), де0.5 <= m < 1іx == m * 2**e. Щоб дістати 53-бітну мантису, домножmна2 ** 53і візьмиint(). Зсув поля порядку — 1023, а розмір мантиси — 52 біти: обидва числа доведеться акуратно узгодити.- Перевіряй на простому. Перш ніж запускати на
0.1, переконайся, що твій розкладач правильно працює на1.0,2.0і0.5— там відповідь легко порахувати в голові. - У 2.1 стеж за типами. Якщо десь випадково з'явиться
/замість//, усе перетвориться наfloat, іassertпро точну суму почне падати на, здавалося б, правильних даних. Це саме та помилка, заради якої писалась ця тема.