Icanup
0% прочитано

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

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

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

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

Я паралельно працювала з двома проєктами: один використовував PHP 8.3, інший був на версії 8.4. У системі в цей момент була активна PHP 8.4, і я випадково запустила тести старішого проєкту без перемикання версії.
До
Результат виконання тесту на 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-значення.

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