Convert between Q31.32 fixed-point world coordinates and double with OneMath’s FixedBridge, exactly, with one documented rounding where one is needed and a clear error outside the range.
A deterministic game host often keeps positions as Q31.32: a 64-bit integer holding the value times 2^32. FixedBridge converts the raw long to and from floating point without depending on any fixed-point type.
using RecursiveAI.OneMath; // A deterministic host stores positions as Q31.32 raw longs (value * 2^32). long x = FixedBridge.FromDouble(1234.5678); Console.WriteLine($"raw {x}, back to double {FixedBridge.ToDouble(x):R}, as float {FixedBridge.ToSingle(x):R}"); // raw -> double -> raw is the identity for every raw value in range. long[] raws = [1, -1, (1L << 53) - 1, 5_000_000_000]; double[] values = new double[raws.Length]; long[] again = new long[raws.Length]; FixedBridge.ToDouble(raws, values); FixedBridge.FromDouble(values, again); Console.WriteLine($"identity: {raws.SequenceEqual(again)}"); // Rounding: nearest, ties to even. Console.WriteLine($"2^-33 -> {FixedBridge.FromDouble(Math.Pow(2, -33))}, 3*2^-33 -> {FixedBridge.FromDouble(3 * Math.Pow(2, -33))}"); // The range is |v| < 2^21. Console.WriteLine($"bound {FixedBridge.ExclusiveMagnitudeBound}, in range 2e6: {FixedBridge.IsInRange(2e6)}, 3e6: {FixedBridge.IsInRange(3e6)}"); Console.WriteLine($"TryFromDouble(3e6): {FixedBridge.TryFromDouble(3e6, out _)}"); try { FixedBridge.FromDouble(double.NaN); } catch (ArgumentOutOfRangeException e) { Console.WriteLine(e.Message); }
raw 5302428325695, back to double 1234.567800000077, as float 1234.5677 identity: True 2^-33 -> 0, 3*2^-33 -> 2 bound 2097152, in range 2e6: True, 3e6: False TryFromDouble(3e6): False The value NaN is outside the exact range: it must be finite with magnitude below 2^21 = 2097152. (Parameter 'value') Actual value was NaN.
The rules
| Conversion | Result |
|---|---|
raw to double (ToDouble) | raw × 2^-32, exact |
raw to float (ToSingle) | the exact value rounded once to float, nearest, ties to even |
double to raw (FromDouble) | round(v × 2^32), nearest integer, ties to even; −0 gives 0 |
float to raw (FromSingle) | widened to double (exact), then as above |
- The range is a finite value with |v| < 2^21 = 2,097,152, or a raw value with |raw| ≤ 2^53 − 1: 21 integer bits and 32 fractional bits are the 53 bits of a double’s significand, so every raw value in range is a double, and raw → double → raw is the identity.
- Outside it,
FromDoubleandToDoublethrowArgumentOutOfRangeExceptionnaming the value and the bound;TryFromDoubleandTryToDoublereturn false instead. - The span forms convert in index order and throw on the first out-of-range element.
2^-33 is exactly half a raw unit, so it rounds to the even neighbor, 0; 3 × 2^-33 is one and a half, and rounds to 2.
More in the fixed-point bridge, including why float32 can’t hold every Q31.32 value.