---
title: "Deterministic floating-point math in C#"
url: https://recursiveai.org/onemath/cookbook/check-machines-agree/
description: "Deterministic floating-point math in C#: replace Math.Sin and friends with correctly rounded functions that give the same bits on every OS, CPU and runtime, then prove it with a hash. Runnable code, with OneMath."
version: "1.0.0"
---

# Deterministic floating-point math in C#

Call `Math64` and `Math32` instead of `Math` and `MathF`, and use OneMath’s vectors and matrices instead of System.Numerics. Every result is then correctly rounded and the same bits on every OS, CPU, runtime and in WebAssembly, so a lockstep game, a replay or a simulation run on two machines stays in sync. The program below shows the difference and prints a hash to compare across machines.

```shell
dotnet new console -o Deterministic && cd Deterministic
dotnet add package RecursiveAI.OneMath --version 1.0.0
```

Put this in `Program.cs` and run `dotnet run -c Release`:

```csharp
using System.Security.Cryptography;
using RecursiveAI.OneMath;

// Math.Sin calls the C library of the OS it runs on. Math64.Sin is correctly rounded:
// the double nearest the exact sine, the same on every machine.
var rng = new Random(1);
int differ = 0;
for (int i = 0; i < 1_000_000; i++)
{
    double x = rng.NextDouble() * 100 - 50;
    if (Math.Sin(x) != Math64.Sin(x)) differ++;
}
Console.WriteLine($"Math.Sin not correctly rounded: {differ:N0} of 1,000,000 inputs");

// A computation built only on OneMath: hash it, compare the hash across machines.
var buffer = new byte[8 * 100_000];
for (int i = 0; i < 100_000; i++)
{
    double t = i * 0.001;
    double v = Math64.Sin(t) * Math64.Exp(-t / 50)
             + Math64.Atan2(t, 3) * Math64.Log1p(t);
    BitConverter.TryWriteBytes(buffer.AsSpan(8 * i), v);
}
Console.WriteLine($"sha256 {Convert.ToHexString(SHA256.HashData(buffer))[..32]}");
```

```shell
dotnet run -c Release
```

```text
Math.Sin not correctly rounded: 1,394 of 1,000,000 inputs
sha256 D72748EDFF396FE462CF2D674C8D342C
```

That count is from Linux x64. Windows and macOS ship other C libraries, so there the count and the inputs can differ. Microsoft documents that `Math` results “may differ between different operating systems or architectures”. `Math64.Sin` doesn’t, and neither does anything built on it.

## Check it on your targets

Run the same build with the CPU’s vector instructions turned off step by step, and with the JIT’s tiering off. The hash doesn’t move:

```shell
DOTNET_EnableAVX512F=0 dotnet run -c Release | tail -1
DOTNET_EnableAVX2=0 dotnet run -c Release | tail -1
DOTNET_EnableHWIntrinsic=0 dotnet run -c Release | tail -1
DOTNET_TieredCompilation=0 dotnet run -c Release | tail -1
```

```text
sha256 D72748EDFF396FE462CF2D674C8D342C
sha256 D72748EDFF396FE462CF2D674C8D342C
sha256 D72748EDFF396FE462CF2D674C8D342C
sha256 D72748EDFF396FE462CF2D674C8D342C
```

Run it on each target you ship (x64 and arm64, NativeAOT, WebAssembly) and compare one line. For a game, hash the simulation state every N frames and exchange it with peers: a mismatch points at the first frame that diverged. OneMath runs its own version of this on every release, the [hash matrix](https://recursiveai.org/onemath/docs/hash-matrix/), and no 1.x release changes an existing result.

## Limits

- **Only OneMath’s own math is covered.** A `Math` or `MathF` call, a `float` sum taken in a different order, or a dependence on thread scheduling anywhere in your code breaks determinism again; when a hash differs, look there.
- **No fixed-point type.** OneMath is floating point; for Q31.32 positions it converts, see [Convert fixed-point to double](https://recursiveai.org/onemath/cookbook/fixed-point-bridge/).

Vectors, matrices and quaternions are in [Vector and quaternion math in C#](https://recursiveai.org/onemath/cookbook/vector-and-quaternion-math/); moving existing System.Numerics code is in [Port System.Numerics code to OneMath](https://recursiveai.org/onemath/cookbook/port-from-system-numerics/).
