Домашнє завдання

Числа й арифметика

Три рівні. Кожен наступний спирається на попередній, але братися можна за будь-який. Пиши в окремому файлі 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"

Підказки