# math

> The exact results of 24 Math operations on fixed inputs, hashed. Their last digits differ between JavaScript engines and engine versions.

Source: https://whiskerjs.com/signals/math/ (last updated 2026-10-26)

**math at a glance (from the library code, signalsVersion 9)**

| Field | Value |
| --- | --- |
| Component name | `math` |
| In the visitorId | yes |
| structuredId group | [browser (engine and build)](https://whiskerjs.com/docs/identifiers/#structuredid) |
| Hashing | always a digest (first 128 bits of SHA-256) |
| Excluded under | no browser class |
| Inner timeout | 1000 ms |
| Added in | [signalsVersion 1](https://whiskerjs.com/whats-new/) |

> **In plain words:**
>
> Computers approximate mathematical functions such as sine or the exponential, and different browsers’ JavaScript engines do it in slightly different ways. For a few carefully chosen inputs, the very last digits of the answer differ. WhiskerJS computes 24 such results and keeps only a short hash of them. It needs no permission and draws nothing; it is pure arithmetic.

## What it reads

The window’s `Math` object, called with fixed inputs:

- `acos`, `asin` of 0.123124234234234242; `acosh` of 1e308; `asinh` of 1; `atanh` and `atan` of 0.5;
- `sin` and `tan` of -1e300; `cos` of 10.000000000123; `sinh`, `cosh`, `tanh`, `exp`, `expm1` of 1; `log1p` of 10;
- “polyfill” forms of some of them written with `log`, `sqrt` and `exp` (for example `log(1e154 + sqrt(1e154 * 1e154 - 1))` for acosh);
- `pow(PI, -100)`.

That is 24 named results in a fixed order.

## How the technique works

1. Each function is bound to the window’s `Math`. A function that is missing is replaced by one that returns 0, so a missing function changes the value instead of failing.
2. Each result is written as `name=<number>` with JavaScript’s default number printing, and the 24 pairs are joined with `;`.
3. The string is always hashed: SHA-256, first 128 bits, 32 hex characters.

The operations, constants and polyfill expressions are adapted from FingerprintJS 5.3.0 (MIT); the notice is kept beside the code and in `THIRD_PARTY_NOTICES.md`.

## Why it helps

The inputs sit where approximation errors show: huge arguments to `sin` and `tan`, values near the edge of the floating-point range, and expressions that lose precision. Different engines (V8, SpiderMonkey, JavaScriptCore), and sometimes different versions of one engine, round the last bits differently. The value says little about the device and a lot about the engine build, which is why it is in the `browser` group.

## Stability

- Stable within one browser version on one device: the same inputs always give the same outputs.
- It can change with a browser update, when an engine changes its maths library. That moves the visitorId and the `browser` part of the structuredId.
- It does not follow user settings.

## Browser support and what blocks it

Every browser has `Math`, so it is `ok` everywhere: [Chrome](https://whiskerjs.com/browsers/chrome-desktop/), [Firefox](https://whiskerjs.com/browsers/firefox/), [Safari](https://whiskerjs.com/browsers/safari-macos/), [iOS browsers](https://whiskerjs.com/browsers/safari-ios/), [Brave](https://whiskerjs.com/browsers/brave/), [Edge](https://whiskerjs.com/browsers/edge/). No browser class excludes it. Because all iOS browsers share WebKit’s engine, they are expected to share the same math results with Safari on the same iOS version.

## Privacy

The value is a property of the browser’s engine build, shared by everyone on that build. It contains nothing about the user and leaves the library only as a digest.

## How WhiskerJS handles noise and failures

| Status | ruleId | When |
| --- | --- | --- |
| `ok` |   | the digest of the 24 results |
| `unavailable` | `no_api` | there is no `Math` object or `Math.exp` is not a function |
| `unavailable` | `no_digest` | `crypto.subtle` is missing, so the value cannot be hashed (never sent raw) |
| `blocked` | `threw`, `security_error` | a patched `Math` function throws |
| `excluded` | `caller_excluded` | you excluded it |
| `timed_out` | `timeout`, `aborted` | budget or abort |

There is no noise to detect: the results are deterministic.

## Example value

In headless test browsers in a Linux container on an ARM host, Chromium and Firefox both gave `93c6779db9af8debbf63b6d97bbcbd97` and WebKit gave `c5a6fcbb6b7bfc2c6aca4d4345d810bb`. A small example, not a measurement of how often engines agree.

## Questions and answers

### Why would Math.sin give different answers in different browsers?

The standard allows small differences in how functions such as sin, tan or exp are approximated. Engines use different algorithms or libraries, so the last digits can differ for some inputs.

### Is the math code taken from another library?

The list of operations, their input constants and the polyfill expressions are adapted from FingerprintJS 5.3.0 under the MIT licence, with the notice kept in THIRD_PARTY_NOTICES.md. The collector code around them is WhiskerJS's own.

### Does math tell the CPU apart?

Mostly it tells engines and their versions apart. Two engines can also agree on the same machine: in a container run, headless Chromium and Firefox produced the same math digest while WebKit differed.

## Related pages

- [cpu_architecture](https://whiskerjs.com/signals/cpu-architecture/): Another pure computation, about the CPU family.
- [browser_class](https://whiskerjs.com/signals/browser-class/): The engine family, from what the browser says.
- [engine_mismatch flag](https://whiskerjs.com/docs/integrity/#engine_mismatch): Cross-checks the claimed engine against how code behaves.
- [Digest](https://whiskerjs.com/glossary/#digest): How large values leave the library.
