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


Скриншоти показують реальний результат запуску одного й того самого тесту на 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-значення.



