Math.tanh rounds the way the claimed OS's libm rounds
Check id math-tanh-os-vs-ua
What an ordinary browser yields
The last bit of Math.tanh matches the libm of the operating system the user agent claims — a browser saying Windows rounds the way UCRT rounds.
What a detector infers
IEEE 754 fixes how a double is stored but does not require transcendental functions to be correctly rounded, so every operating system's C math library ships its own polynomial approximation and they disagree in the final bit — the two largest implementations differ on roughly a quarter of all inputs, usually by one unit in the last place. For years this was invisible from JavaScript because V8 bundled its own copy of fdlibm and therefore computed the same answer everywhere. That changed in Chrome 148: V8 swapped Math.tanh to std::tanh, which calls straight through to whatever libm the browser is linked against — glibc on Linux, libsystem_m on macOS, UCRT on Windows. The check evaluates tanh at 0.7, 0.8 and 0.9 and requires all three to match one platform's measured constants; tanh(0.8) separates all three operating systems in a single call, and the other two corroborate it. tanh(0.5) is deliberately excluded because it is identical everywhere and carries no information. What makes this among the strongest signals available is where the answer comes from. It is not a value the browser reports about itself — it is arithmetic performed by a C library underneath the JavaScript engine, so no navigator override, no CDP patch, no UA-CH shim and no launch flag reaches it. A headless Chrome on a Linux server presenting a Windows user agent is contradicted by its own floating point. Note also what the check refuses to do: an unrecognised set of constants — musl, a BSD libc, Apple Silicon's vector routines — reports nothing rather than guessing, and Math.tanh is the only Math function worth reading this way, since every other one still routes through bundled llvm-libc and is byte-identical across platforms. Technique documented by Scrapfly; the constants were verified against a real Windows Chrome 148 before shipping.
How to resolve it
There is no JavaScript fix, and that is the point: since Chrome 148 this value is produced by the C library the browser links against, below anything a page, an extension or CDP can reach. Either run the browser on the OS it claims, or claim the OS it is actually running on — the arithmetic will keep disagreeing no matter what userAgent, userAgentData and navigator.platform are set to. The check reports N/A rather than a failure on Chrome 147 and earlier, where tanh is still bundled fdlibm, and on Android, ChromeOS and iOS, whose libm this check has no measured signature for. It also declines when a desktop UA arrives on touch-primary hardware: Chrome for Android's Request-desktop-site rewrites the UA to the Linux desktop string, which would otherwise defeat the Android decline and adjudicate Bionic's arithmetic against a table holding only glibc, libsystem_m and UCRT.
Read in the wild by
Scrapfly
Published the technique: V8 commit c1486295ae5 (Chrome 148) swapped Math.tanh to std::tanh, routing it through the host libm, so the last bit of tanh names the operating system. Their table was re-verified here against a real Windows Chrome 148 before shipping.
Math.tanh(0.8): 0.6640367702678491 Linux/glibc · 0.664036770267849 macOS/libsystem_m · 0.6640367702678489 Windows/UCRT
Scrapfly — Math-based OS fingerprintingEvery attribution traces to a published artifact. See the sources and their limits.
Other checks in Engine & OS oracles
- The measured JS engine matches the engine the User-Agent claims
js-engine-identifier-vs-uaTwo values are read straight out of the JavaScript engine itself: the character length of the RangeError message thrown by (-1).toFixed(-1)… - which OS hyphenation engine the browser hyphenates through
hyphenation-engine-osWhen CSS asks a browser to hyphenate a long word, the break points come from a dictionary supplied by the operating system — and the… - the CPU architecture matches the architecture the browser claims
cpu-arch-nan-vs-uachIEEE-754 fixes the exponent and the quiet bit of a NaN but leaves the sign bit to the hardware, and the hardware disagrees: an x86 FPU… - the three heap numbers describe a possible heap
jsheap-triplet-orderingThis check replaces a retired one, and the swap is the lesson. - the platform's system font belongs to the operating system claimed
system-font-vs-claimed-osThe CSS2 system-font keywords — caption, icon, menu, message-box, small-caption and status-bar — are resolved by the HOST platform's font… - typefaces the claimed OS ships as separate faces measure as separate faces
claimed-os-typefaces-are-distinctA font request is answered by the host's font stack, not by the browser and not by anything the user agent sets, so changing…
Clearcote is a browser built for fingerprint coherence
It is a Chromium fork, maintained by the same people who wrote this reference. It ships as a compiled browser rather than as a stealth script injected into someone else's — which is a description of how it is built, and is not an argument about how it behaves on this check.
This audit takes no position on how Clearcote scores on Engine & OS oracles checks, on this one, or anywhere else. It has no baseline corpus of other people's fingerprints to rank you against and no vendor scoreboard — nearly every check is self-referential, asking one browser the same question through two independent APIs and reporting whether both answers can be true at once. It runs identically on any browser, including ours. Run it on yours and read the result yourself.
See the other checks in Engine & OS oracles — the family math-tanh-os-vs-ua belongs to.