Skip to content

Commit f15ca36

Browse files
committed
chore: doc update
1 parent 5314d0b commit f15ca36

2 files changed

Lines changed: 86 additions & 0 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Floating-point math is fast, hardware-accelerated, and the right choice for rend
2121

2222
Fixed-point math stores numbers as scaled integers. In FixedMathSharp, `Fixed64` uses a Q32.32 layout: 32 bits for the whole-number side and 32 bits for the fractional side. That trade gives you deterministic arithmetic with predictable rounding behavior, at the cost of less dynamic range than `double` and less raw throughput than native floating point.
2323

24+
For the exact raw layout, range, and precision trade-offs, see [`docs/wiki/fixed64-representation.md`](docs/wiki/fixed64-representation.md).
25+
2426
Use FixedMathSharp when you need:
2527

2628
- Lockstep multiplayer, replay systems, rollback, or deterministic simulation.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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

Comments
 (0)