0% прочитано

Я випадково запустила проєкт на PHP 8.4 і тести показали різницю в round()

Невелике спостереження про зміну поведінки round() між PHP 8.3 та PHP 8.4, яке я випадково помітила під час роботи над двома різними проєктами.

20 липня 2026 р. 2 хв читанняPHP

Неочікуваний збій тестів

Я паралельно працювала з двома проєктами: один використовував PHP 8.3, інший був на версії 8.4. У системі в цей момент була активна PHP 8.4, і я випадково запустила тести старішого проєкту без перемикання версії.

Саме такі помилки з перемиканням версій пізніше підштовхнули мене налаштувати окремий Laradock workflow для кількох PHP versions, де потрібний PHP вибирається автоматично для кожного проєкту.

До
Результат виконання тесту на PHP 8.3
Після
Результат виконання тесту на PHP 8.4

Скриншоти показують реальний результат запуску одного й того самого тесту на PHP 8.3 та PHP 8.4. Жодних змін у коді, лише інша версія інтерпретатора.

Мінімальний тест, що відтворює різницю
<?php declare(strict_types=1); namespace Tests\Unit; use PHPUnit\Framework\TestCase; final class RoundBehaviorTest extends TestCase{    public function testRoundBehaviorBetweenPhpVersions(): void    {        $totalCommission = 123.456 + 78.999;        $commissionWithoutVat = round(168.7125, 2);         $commissionVat = round(            $totalCommission - $commissionWithoutVat,            2,        );         $this->assertSame(33.75, $commissionVat);    }}

Що змінилося в PHP 8.4

Найцікавіше виявилося те, що в обох версіях PHP перед викликом round() використовувалося однакове проміжне значення 33.74499999999997613.

На PHP 8.3 воно округлювалося до 33.75, а на PHP 8.4 - до 33.74. Саме через цю різницю тест, який раніше проходив, почав падати.

Це сталось тому, що в PHP 8.4 змінилася обробка граничних значень у round(). Раніше функція намагалася поводитися так, ніби float є точним десятковим числом, і могла відносити значення, розташовані дуже близько до межі округлення, до наступної половини.

Починаючи з PHP 8.4, round() враховує фактичне двійкове значення float. У цьому прикладі до функції потрапляє не точне 33.745, а:

33.74499999999997613

Це число трохи менше за 33.745, тому при округленні до двох знаків результатом стає 33.74.

PHP 8.3 компенсувала цю похибку й повертала 33.75, тоді як PHP 8.4 більше не трактує таке значення як точну половину. Саме ця зміна в обробці edge cases і спричинила падіння тесту.

Окремо PHP 8.4 також додала enum RoundingMode і чотири нові режими округлення, але в цьому конкретному тесті причина різниці не в обраному режимі, а саме в новій обробці граничного float-значення.

Цей випадок також став для мене хорошим нагадуванням, чому під час оновлення Laravel-проєкту на нову PHP version недостатньо просто перевірити, що dependencies встановлюються: важливо прогнати реальні тести й подивитися, чи не змінилася поведінка самого runtime.

Я не шукала різницю між PHP 8.3 та PHP 8.4. Просто працювала над проєктом. Саме за такі моменти я люблю автоматизовані тести. Вони іноді помічають те, що людина ще навіть не почала підозрювати.