|
| 1 | +# Fixed64 Representation |
| 2 | + |
| 3 | +`Fixed64` is the scalar foundation of FixedMathSharp. It stores a deterministic fixed-point value in a signed 64-bit raw integer using a Q32.32 layout. |
| 4 | + |
| 5 | +## Q32.32 Layout |
| 6 | + |
| 7 | +The library-wide shift amount is fixed at 32 bits: |
| 8 | + |
| 9 | +```csharp |
| 10 | +public const int SHIFT_AMOUNT_I = 32; |
| 11 | +public const long ONE_L = 1L << SHIFT_AMOUNT_I; |
| 12 | +``` |
| 13 | + |
| 14 | +That means one whole unit is stored as `4,294,967,296` raw units. Conceptually: |
| 15 | + |
| 16 | +```text |
| 17 | +value = rawValue / 2^32 |
| 18 | +rawValue = value * 2^32 |
| 19 | +``` |
| 20 | + |
| 21 | +The lower 32 bits provide the fractional resolution. The upper side of the signed 64-bit value provides the whole-number range through normal two's-complement signed integer behavior. |
| 22 | + |
| 23 | +## Scaled Integers, Not Stored Fractions |
| 24 | + |
| 25 | +`Fixed64` does not store a whole-number part and a fractional object separately. It stores one signed integer, and the library interprets that integer through the fixed Q32.32 scale. |
| 26 | + |
| 27 | +For example, `0.5` is stored as raw `0x00000000_80000000`, or `1L << 31`. That value is an integer. It means one half only because `Fixed64` interprets raw values as units of `1 / 2^32`. |
| 28 | + |
| 29 | +This is the useful mental model: |
| 30 | + |
| 31 | +```text |
| 32 | +0.5 -> raw 2,147,483,648 |
| 33 | +1.0 -> raw 4,294,967,296 |
| 34 | +1 raw bit -> 1 / 4,294,967,296 |
| 35 | +``` |
| 36 | + |
| 37 | +Addition and subtraction can operate directly on raw values because both operands already share the same scale. Multiplication and division need rescaling because multiplying two Q32.32 values produces an intermediate value with twice as many fractional bits, while division needs enough shifted numerator precision before the quotient is taken. |
| 38 | + |
| 39 | +So the fractional part does not disappear during bit shifts. It was never a separate stored thing. The fraction is the interpretation of the scaled integer, much like inches, centimeters, frames, or ticks are interpretations of a count at a chosen measurement scale. |
| 40 | + |
| 41 | +## Range and Resolution |
| 42 | + |
| 43 | +With Q32.32: |
| 44 | + |
| 45 | +- Smallest positive raw step: `1 / 2^32`, approximately `0.00000000023283064365`. |
| 46 | +- `Fixed64.One`: raw `0x00000001_00000000`. |
| 47 | +- `Fixed64.MinIncrement`: raw `0x00000000_00000001`. |
| 48 | +- `Fixed64.MIN_VALUE`: raw `long.MinValue`, exactly `-2147483648`. |
| 49 | +- `Fixed64.MAX_VALUE`: raw `long.MaxValue`, just under `2147483648`. |
| 50 | + |
| 51 | +Example raw values: |
| 52 | + |
| 53 | +| Raw value | Meaning | |
| 54 | +| --- | --- | |
| 55 | +| `0x00000000_00000000` | `0` | |
| 56 | +| `0x00000001_00000000` | `1` | |
| 57 | +| `0x00000000_00000001` | `1 / 2^32` | |
| 58 | +| `0x7FFFFFFF_FFFFFFFF` | Maximum representable positive value | |
| 59 | +| `0x80000000_00000000` | Minimum representable negative value | |
| 60 | + |
| 61 | +## Why This Trade-Off? |
| 62 | + |
| 63 | +Q32.32 favors fine fractional precision while keeping a large enough whole-number range for many deterministic simulations, games, procedural systems, and tools. The trade-off is deliberate: |
| 64 | + |
| 65 | +- More fractional bits reduce quantization error in small movements, rotations, interpolation, and accumulated simulation steps. |
| 66 | +- Fewer whole-number bits than `double` or a wider integer-backed fixed type mean large worlds should usually use local coordinates, chunk-relative positions, rebasing, or domain-specific scaling. |
| 67 | +- Arithmetic must guard overflow because multiply, divide, trigonometry, and interpolation can temporarily need more intermediate range than the final 64-bit value can store. |
| 68 | + |
| 69 | +FixedMathSharp handles these cases with deterministic, guarded algorithms. For example, `Fixed64` multiplication uses full-width intermediate precision and saturating behavior for overflow paths. |
| 70 | + |
| 71 | +## What About Other Shift Amounts? |
| 72 | + |
| 73 | +A different fixed-point layout would choose a different range/precision balance. For example, a Q48.16 style layout would have much more whole-number range and much less fractional precision. |
| 74 | + |
| 75 | +FixedMathSharp does not currently expose `SHIFT_AMOUNT_I` as a user-configurable setting. Changing it would affect constants, arithmetic algorithms, serialization compatibility, test expectations, and the practical behavior of every numeric type built on `Fixed64`. |
| 76 | + |
| 77 | +Use Q32.32 when deterministic precision is more important than enormous coordinate range. If an application needs much larger absolute values, prefer changing the domain scale or coordinate system before changing the numeric representation. |
| 78 | + |
| 79 | +## Practical Guidance |
| 80 | + |
| 81 | +- Use `Fixed64.FromRaw(long)` only when you intentionally want an exact raw representation. |
| 82 | +- Use constructors, constants, and helpers such as `Fixed64.One`, `Fixed64.Fraction`, and `FixedMath` methods for normal value-space code. |
| 83 | +- Keep deterministic simulation state in fixed-point values, but convert to `float` or `double` at rendering and engine interop boundaries when needed. |
| 84 | +- Treat overflow behavior as part of the numeric contract; avoid relying on primitive floating-point intuition for extreme values. |
0 commit comments