---
title: "Port System.Numerics code to OneMath"
url: https://recursiveai.org/onemath/cookbook/port-from-system-numerics/
description: "Port System.Numerics matrix and vector code to deterministic math in C# without silently transposing your transforms: the conventions are opposite, and the conversions handle it. Runnable code, with OneMath."
version: "1.0.0"
---

# Port System.Numerics code to OneMath

System.Numerics uses row vectors and OneMath uses column vectors, so reverse the order of every matrix product and convert with `FromSystemNumerics` and `ToSystemNumerics`, never field by field. The program below builds the same transform both ways and shows what a field-by-field copy does.

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

Put this in `Program.cs` and run `dotnet run`:

```csharp
using RecursiveAI.OneMath;

float angle = MathF.PI / 2;

// System.Numerics (row vectors): T * R translates first, then rotates.
var sn = System.Numerics.Matrix4x4.CreateTranslation(10, 0, 0) * System.Numerics.Matrix4x4.CreateRotationY(angle);
// OneMath (column vectors): the same transform is R * T.
var m = Matrix4x4F.CreateRotationY(angle) * Matrix4x4F.CreateTranslation(new Vector3F(10, 0, 0));

var p = new Vector3F(1, 0, 0);
Console.WriteLine($"System.Numerics: {System.Numerics.Vector3.Transform(p.ToSystemNumerics(), sn)}");
Console.WriteLine($"OneMath:         {m.TransformPoint(p)}");

// Where the translation lives.
Console.WriteLine($"System.Numerics translation M41..M43: {sn.M41}, {sn.M42}, {sn.M43}");
Console.WriteLine($"OneMath translation M14, M24, M34:    {m.M14}, {m.M24}, {m.M34}");

// Convert, don't copy fields: both directions transpose.
Matrix4x4F same = Matrix4x4F.FromSystemNumerics(sn);
Console.WriteLine($"FromSystemNumerics(sn) == m: {same == m}");
Console.WriteLine($"Round trip equal: {Matrix4x4F.FromSystemNumerics(m.ToSystemNumerics()) == m}");

// Copying fields one to one gives the transpose: a different transform.
var copied = new Matrix4x4F(sn.M11, sn.M12, sn.M13, sn.M14, sn.M21, sn.M22, sn.M23, sn.M24,
                            sn.M31, sn.M32, sn.M33, sn.M34, sn.M41, sn.M42, sn.M43, sn.M44);
Console.WriteLine($"Field copy:      {copied.TransformPoint(p)}");
```

```text
System.Numerics: <-4.808253E-07, 0, -11>
OneMath:         (-4.808253E-07, 0, -11)
System.Numerics translation M41..M43: -4.371139E-07, 0, -10
OneMath translation M14, M24, M34:    -4.371139E-07, 0, -10
FromSystemNumerics(sn) == m: True
Round trip equal: True
Field copy:      (-4.371139E-08, 0, 1)
```

## The rule

- **OneMath uses column vectors**: `m * v` transforms the column `v`, a 4x4’s translation sits in column 4 (`M14`, `M24`, `M34`), and `a * b` applies `b` first.
- **System.Numerics uses row vectors**: translation in `M41`…`M43`, and `a * b` applies `a` first. The field names `Mrc` are the same, which is what makes a field-by-field copy look right and transform wrongly.
- So reverse the operand order of every product, and convert with `FromSystemNumerics` and `ToSystemNumerics`, which transpose. Both directions round-trip exactly.

Vectors and quaternions convert with the same pair of methods; quaternions keep their components and their Hamilton product. Converting from double to float is only the explicit `ToSystemNumerics()` and rounds to nearest even.

## Why port

Both libraries give `-11` here. The difference is the promise: OneMath’s results are the same bits on every OS, CPU and runtime, and System.Numerics makes no such promise. Keep System.Numerics for rendering if you like, and use OneMath for whatever the simulation, a replay or a lockstep peer has to agree on.

## Limits

- **Conversions are to System.Numerics only.** Unity, MonoGame and other engines have their own types: convert their fields through System.Numerics, minding each engine’s convention.
- **Converting double to float rounds.** It is explicit, `ToSystemNumerics()`, and rounds to nearest even.
