Learning
Všechny články
Frontend13. července 2026 5 min

Math v prohlížeči přestal být bitově přenosný

Proč Math.tanh(0.8) vrací na Linuxu, macOS a Windows tři různá čísla

Stejný kód, tři různé bity podle operačního systému

Math.tanh(0.8) vrátí na Linuxu 0.6640367702678491, na macOS 0.664036770267849 a na Windows 0.6640367702678489. Stejný engine, stejná verze, jiný poslední bit. Není to bug. Je to důsledek toho, že přenositelnost výsledku, na kterou ses roky spoléhal, byla jen vedlejší produkt jednoho rozhodnutí ve V8 — a to rozhodnutí padlo.


Co IEEE 754 slibuje a co ne

Standard IEEE 754 vyžaduje správné zaokrouhlení u základních operací: sčítání, odčítání, násobení, dělení, odmocnina. Tam ti dva různé stroje musí vrátit tentýž bit.

U transcendentních funkcí — sin, cos, tanh, exp — nevyžaduje nic. Standard nechává vendorům volnost, jak je spočítají. Každý operační systém proto dodává vlastní matematickou knihovnu: Linux glibc, macOS libsystem_m, Windows ucrtbase.dll. Používají jiné minimaxové polynomy a jiné lookup tabulky. Výsledek se liší v jednotkách posledního místa (ULP).

To tu bylo vždycky. Nová je jen cesta, kterou se ten rozdíl dostal do JavaScriptu.


Proč to praská až teď

Do verze 148 počítal Chrome Math.tanh přes vlastní port fdlibm přibalený rovnou ve V8. Ten kód byl na všech platformách totožný, takže vracel všude stejné bity. Přenositelnost nebyla záruka jazyka — byla to náhoda implementace.

Commit c1486295ae5 ve V8 ten port nahradil voláním std::tanh, které sáhne na hostitelskou libm. Od té chvíle JavaScript volá nativní funkci daného systému a rozdíl mezi knihovnami prosákne až k tobě. Abstrakce Math.tanh byla vždycky trochu děravá; teď tou dírou teče identita OS.


Jeden výpočet místo složitého probingu

Pro anti-bot systémy je to dárek. Nepotřebují měřit časování ani zkoušet exotické API. Stačí zavolat Math.tanh na vhodném vstupu a porovnat bity s tabulkou: pravý macOS Chrome dává jeden vzor, pravý Linux jiný. Scraper, který se v User-Agentu vydává za macOS, ale vrátí linuxové bity, si sám odporuje jediným voláním funkce.

Druhá strana téže mince je přenositelnost. Pokud tvůj kód předpokládá, že stejný vstup dá na klientovi a na serveru stejný výstup — validace, deterministické simulace, cokoli, co porovnává čísla na přesnou shodu — tenhle předpoklad právě přestal platit napříč platformami. Math.tanh už není referenční bod, o který se dá opřít.


Co si z toho odnést

„Correctly rounded" není u transcendentních funkcí záruka, je to laskavost, kterou ti někdo dělal. Kdykoli tvůj kód porovnává výsledek Math-funkce na přesnou shodu mezi dvěma stroji, stojíš na písku — a to platí i uvnitř jednoho prohlížeče, jen na dvou operačních systémech. Když potřebuješ determinismus, nespoléhej na nativní Math; použij vlastní implementaci se známým zaokrouhlením, nebo porovnávej s tolerancí v ULP. A když stavíš fingerprinting nebo se mu bráníš, ber v úvahu, že i pár posledních bitů čísla je signál.

Poslech místo čtení — 0 zdroje k načtení.