🧐 PHP, Liczby zmiennoprzecinkowe i Tajemnica -0.0 Przegląd PHP #5

Tak, serio. -0.0 i 0.0 to dwie różne reprezentacje bajtowe w standardzie IEEE 754 – ale w praktyce… PHP i tak je traktuje tak samo.

Miałem taki kod:

public function getPercentage(): int
{
    // epsilon to avoid division by zero
    if (abs($this->limitPrice) < 1e-8) {
        return 0;
    }

    return min(100, (int) round(($this->totalCartPrice / $this->limitPrice) * 100));
}

I zacząłem się zastanawiać:

  • Czy mogę po prostu porównać $this->limitPrice == 0.0?
  • A co jeśli limitPrice = -0.0? 🤯

🔬 Analiza

1️⃣ IEEE 754 i -0.0

W standardzie liczb zmiennoprzecinkowych istnieje dodatnie zero (0.0) i ujemne zero (-0.0).
Na poziomie bajtów to dwie różne wartości:

  • 0.00x0000000000000000
  • -0.00x8000000000000000

Czyli tak, binarnie to nie to samo.


2️⃣ Co na to PHP?

Porównajmy:

var_dump(0.0 == -0.0);   // true
var_dump(0.0 === -0.0);  // true

✅ PHP traktuje te dwie wartości jako równe zarówno w porównaniu luźnym (==), jak i ścisłym (===).

Ale uwaga:

var_dump(1 / 0.0);  // float(INF)
var_dump(1 / -0.0); // float(-INF)

➡️ Przy dzieleniu widać różnicę – znak zera wpływa na wynik (+∞ vs -∞).


3️⃣ Czy to ma znaczenie w moim przypadku?

W moim kodzie limitPrice ma maksymalnie 4 miejsca po przecinku, pochodzi z bazy danych lub prostych operacji.
Nie mam tu ujemnych zer z kosmosu.
W praktyce mogę spokojnie napisać:

if ($this->limitPrice == 0.0) {
    return 0;
}

I wszystko działa jak należy.
Epsilon (1e-8) nie jest potrzebny, bo nie mam problemów z błędami zaokrągleń na poziomie 17 miejsc po przecinku.


🛠 Bonus: Jak wykryć -0.0 (dla nerdów)

Jeżeli jednak z jakiegoś powodu musisz wiedzieć, czy to -0.0, oto trik:

function isNegativeZero(float $x): bool {
    return $x === 0.0 && 1 / $x === -INF;
}

var_dump(isNegativeZero(0.0));  // false
var_dump(isNegativeZero(-0.0)); // true

Tak, trzeba podzielić przez zero, żeby to wykryć. 🧪


✅ Wniosek

  • Tak, -0.0 i 0.0 to różne bajty.
  • PHP traktuje je jako równe (==, ===).
  • Możesz śmiało pisać == 0.0 w porównaniach.
  • Jedynie dzielenie przez 0.0 i -0.0 zwraca różne nieskończoności (INF vs -INF) – więc jak dzielisz, warto używać abs().

🎉 Podsumowanie

Udało mi się uprościć kod i zamiast epsilonów mam po prostu:

public function getPercentage(): int
{
    return $this->limitPrice ?
        : min(100, (int) round(($this->totalCartPrice / $this->limitPrice) * 100)) : 0;
}

Czytelniej, prościej i dalej bezpiecznie.
A fakt, że -0.0 istnieje, zostawiam jako ciekawostkę do impressowania znajomych programistów na kawie. ☕️😎

PS: Jeżeli zależy Ci na użyciu epislona to chyba znacznie lepiej użyć tej stałej.

PHP_FLOAT_EPSILON



Podoba Ci się ten artykuł?

Oto kilka następnych artykułów, które mogą Ci się również spodobać:

  • PhpUnit willReturnMap - Przegląd PHP #4
  • BackedEnum - Przegląd PHP #3
  • Funkcje tablicowe: inicjalizacja tablicy przy pomocy [...] lub array()? - Przegląd PHP #2
  • Funkcje tablicowe: ['array_filter','array_merge'] - Przegląd PHP #1
  • Testowanie obłsugi błedów w Godocie przy pomocy GUT