16. Jak docílit stejných číselných výsledků v PHP i v JS

Ilustrační obrázek k článku: Jak docílit stejných číselných výsledků v PHP i v JS
Než začneme programovat nějaké matematické výpočty je potřeba zjistit zda vůbec můžeme v PHP i v JS dojít při těchto výpočtech ke stejným výsledkům.

V jednom z předchozích článků jsme si řekli, že od našeho jazyka budeme očekávat, že vrací stejné výsledky pro PHP i JS. Na první pohled to vypadá jasně – funkce vrací stejná čísla, stringy, pole atd. Ale první zádrhel nastává už u desetinných čísel. Porovnejme:

PHP:
  1. echo 1/7; //0.14285714285714
JS:
  1. alert(1/7); //0.14285714285714285

Na základě výše uvedeného by se mohlo zdát, že PHP a JS počítají s různou přesností, a že naprosté shody výsledků není možné dosáhnout. Jenže to není pravda. Pokud jazykům přesně řekneme, na kolik desetinných míst chceme výsledek zobrazit, dostaneme nejen stejné výstupy – ale dokonce i naprosto stejné chyby.

PHP:
  1. echo number_format(1/3, 25, '.', ''); //0.3333333333333333148296163
JS:
  1. alert((1/3).toFixed(25)); //0.3333333333333333148296163

Jak je to možné? Oba jazyky používají čísla ve formátu IEEE 754, tedy 64bitová desetinná čísla, známá také jako double. Podrobněji to najdeme v dokumentaci PHP a JS.

Proč se ale výchozí zobrazení liší? Je to dáno tím, že implicitní převod na string probíhá v každém jazyce trochu jinak. PHP je v tomto ohledu „opatrnější“ a standardně zobrazuje méně desetinných míst. Přesnost výstupu lze navíc v PHP měnit pomocí direktivy precision, kterou lze upravit v php.ini nebo příkazem ini_set.

Mohlo by se tedy zdát, že úpravou precision dokážeme zajistit stejný výstup jako v JS. Ale nestačí to. Existuje totiž celá řada dalších rozdílů:

Příklad:

PHP:
  1. echo 100000000000000.0; //1.0E+14 - Už čtrnáctimístná převádí na exponenciální tvar
  2. echo 1E30; //1.0E+30 - velké "E", přidává zbytečnou nulu za desetinnou tečkou
JS:
  1. alert(100000000000000.0); //100000000000000
  2. alert(1E30); //1e+30 - malé "e", bez nuly za desetinnou tečkou
Pokud požadujeme, aby PHP a JS převáděly čísla typu double na string stejným způsobem, musíme si tento převod naprogramovat sami.

Formát double rozebírat podrobně nebudeme – je popsán např. na výše zmíněné Wikipedii. Důležité je, že se skládá z mantisy a exponentu, což má za následek nerovnoměrné rozložení hodnot na číselné ose (větší čísla jsou řidší). Právě na tuto vlastnost často narážíme ve známých vtípcích:

PHP:
  1. var_dump(0.1 + 0.2 === 0.3); //false
JS:
  1. alert(0.1 + 0.2 === 0.3); //false

Často to bývá doprovázeno poznámkami typu „PHP neumí počítat ani do 0.3“. Ale realita je taková, že výpočet provádí hardwarový matematický koprocesor – dříve samostatná součástka, dnes součást každého CPU. Důležité je, že se tak chová jak v JS, tak v PHP.

Ovšem ne vždy dopadnou podobné ukázky stejně:

JS:
  1. alert(10000000000000000 === 10000000000000001); //true :-)
PHP:
  1. var_dump(10000000000000000 === 10000000000000001); //false

V JavaScriptu se obě čísla považují za stejná. Jsou už tak „daleko“ na číselné ose, že jsou reprezentována jednou hodnotou. PHP je však vidí jako rozdílná. Proč?

PHP totiž používá nejen čísla typu double, ale i integer – formát určený pro celá čísla, který dokáže přesně reprezentovat mnohem větší hodnoty.

Ale v našem programu chceme, aby výsledek byl v PHP i JS stejný. Jak na to?

Nejjednodušší cestou je donutit PHP, aby také použilo typ double (v PHP označovaný jako float). Převod zajistíme třeba přičtením 0.0 nebo zápisem s desetinnou tečkou:

PHP:
  1. var_dump(10000000000000000.0 === 10000000000000001.0); //true
JS:
  1. alert(10000000000000000.0 === 10000000000000001.0); //true

Zapamatujme si tedy:

Pokud požadujeme v PHP a v JS stejné výsledky, musíme pro oba jazyky zvolit stejný typ číselné reprezentace tj. double.
Předchozí