#[repr(C)]pub struct Fix128 {
pub hi: i64,
pub lo: u64,
}Expand description
128-bit fixed-point number (64 integer bits, 64 fractional bits)
Internal representation: value = raw / 2^64
This provides:
- Range: ±9.2 × 10^18
- Precision: ~5.4 × 10^-20
- Bit-exact arithmetic across all platforms
Fields§
§hi: i64Raw 128-bit value (stored as two i64s for portability) Interpretation: (hi << 64) | lo as a signed 128-bit integer The decimal point is between hi and lo
lo: u64Fractional part (64-bit unsigned)
Implementations§
Source§impl Fix128
impl Fix128
Sourcepub const fn from_raw(hi: i64, lo: u64) -> Self
pub const fn from_raw(hi: i64, lo: u64) -> Self
Create from raw parts (hi = integer, lo = fraction)
Sourcepub fn from_ratio(num: i64, denom: i64) -> Self
pub fn from_ratio(num: i64, denom: i64) -> Self
Create from fraction (numerator / denominator)
Sourcepub fn abs(self) -> Self
pub fn abs(self) -> Self
Absolute value
§Claims
|x|for every value except the most negative one- saturating:
|-2^63| = 2^63is not representable, soabsof the most negative value (hi == i64::MIN,lo == 0) is the largest representable value instead of wrapping back to itself; the result is never negative (AUD-A-S1W5-028, AUD-A-S3W3-010, AUD-A-S5W2-017)
Sourcepub const fn is_negative(self) -> bool
pub const fn is_negative(self) -> bool
Check if negative
Sourcepub const fn ceil(self) -> Self
pub const fn ceil(self) -> Self
Ceiling (round toward positive infinity)
§Claims
- an integer value is returned unchanged; otherwise the next integer up
- saturating: above
i64::MAX(integer parti64::MAXwith a fraction) the next integer is not representable, and the result is the largest representable value, which is still>= self(AUD-A-S1W5-028)
Sourcepub fn sqrt(self) -> Self
pub fn sqrt(self) -> Self
Square root, exact floor: floor(sqrt(self)) in I64F64.
Restoring digit-recurrence (one result bit per step) on the 192-bit
radicand self << 64, 96 fixed steps, so the result is the largest
r with r * r <= self — bit-identical on every platform and to the
pre-1.2.0 Newton-Raphson implementation (verified over 20 000 samples
plus edge values in tests::sqrt_matches_newton_reference).
Before 1.2.0 this ran 64 Newton iterations, each calling the 64-step
long division, ≈ 4 096 inner steps per call (≈ 10 µs); 3-5 iterations
had already converged. The recurrence needs 96 shift/compare/subtract
steps (≈ 24× faster, see benches/physics_bench.rs::fix128_sqrt).
Negative input returns ZERO (deterministic, no NaN).
Deterministic: fixed number of iterations, integer-only.
Sourcepub const fn shr_bits(self, i: u32) -> Self
pub const fn shr_bits(self, i: u32) -> Self
Arithmetic right shift of the full 128-bit value by i bits (exact self / 2^i,
rounding toward negative infinity).
The single source of truth for the CORDIC x >> i / y >> i micro-rotations
(cordic_sin_cos and cordic_atan). Before 1.1.1 cordic_atan had its own copy
that dropped the bits carried from hi into lo, so ONE >> 1 evaluated to 0
instead of 0.5 and every atan / atan2 result was off by up to ~0.17 rad.
Sourcepub fn sin(self) -> Self
pub fn sin(self) -> Self
Sine using CORDIC algorithm (deterministic)
Input should be in range [-π, π] for best precision
Sourcepub fn cos(self) -> Self
pub fn cos(self) -> Self
Cosine using CORDIC algorithm (deterministic)
Input should be in range [-π, π] for best precision
Sourcepub unsafe fn add_simd(self, rhs: Self) -> Self
pub unsafe fn add_simd(self, rhs: Self) -> Self
Addition through the simd feature’s API surface — scalar-equivalent.
Bit-identical to self + rhs. SSE2’s _mm_add_epi64 adds the two
64-bit lanes independently and cannot carry from lo into hi, so a
Fix128 add has no profitable SSE2 form; LLVM already emits add/adc
for the scalar path. Before 1.2.0 this function loaded both operands
into __m128i registers and then discarded them (let _ = (a, b)),
i.e. it was scalar with dead intrinsics.
§Safety
No preconditions; kept unsafe + #[target_feature(enable = "sse2")]
for signature compatibility with existing callers.
Sourcepub unsafe fn sub_simd(self, rhs: Self) -> Self
pub unsafe fn sub_simd(self, rhs: Self) -> Self
Subtraction through the simd feature’s API surface — scalar-equivalent
(see Self::add_simd).
§Safety
No preconditions; kept unsafe + #[target_feature(enable = "sse2")]
for signature compatibility with existing callers.
Source§impl Fix128
impl Fix128
Sourcepub fn checked_mul(self, rhs: Self) -> Option<Self>
pub fn checked_mul(self, rhs: Self) -> Option<Self>
Mul と同じ積を計算し、範囲外なら None を返す
§なぜ必要か (WM-01 / doctrine B-12)
Mul は全段 wrapping_* なので 範囲外の積が silent に巻き戻る
実測 (2026-09-30): 2⁴⁰ × 2⁴⁰ は exact 1.21e24 だが、Q64.64 では
hh = 2⁸⁰ で 2⁸⁰ mod 2⁶⁴ = 0 なので hi が厳密に 0 になる
⇒ position += velocity * dt が 0 加算になり、update_velocities が
velocity = (position − prev_position) * inv_dt で速度も 0 に上書きする
⇒ 「原点で完全に静止した自己整合な状態」 に落ちる
⚠️ 静止は物理的に妥当なのでどの不変条件でも red にならないため、
「値が大きい / 値が飛んだ」を見る検出器では原理的に捕まえられない
⚠️ Mul 自体の挙動は変えない — 既存の決定論 golden と bit 互換を
保つため、検出が要る経路だけが本 method を使う
(PhysicsWorld::step の積分と速度導出、PhysicsWorld::overflow_detected)
Sourcepub fn checked_add(self, rhs: Self) -> Option<Self>
pub fn checked_add(self, rhs: Self) -> Option<Self>
Add と同じ和を計算し、範囲外 (|a + b| ≥ 2⁶³) なら None を返す
Add は wrap するので、範囲の検査が要る経路だけが本 method を使う
(Add 自体の挙動は変えない)
Sourcepub fn powf_pos(self, exponent: Self) -> Self
pub fn powf_pos(self, exponent: Self) -> Self
self^exponent for self > 0 and exponent ≥ 0 (real exponent).
Integer part of the exponent by repeated multiplication, fractional
part by its binary expansion base^f = Π base^(1/2^k) over the first 24
bits using successive Fix128::sqrt calls — pure integer arithmetic,
bit-identical on every platform, relative error ≲ |ln base| · 2⁻²⁴ plus
sqrt truncation (measured ≤ 1e-6 relative for results ≥ 1e-9 over
base ∈ [1e-3, 1e3], exponent ∈ [0, 8],
math::tests::powf_pos_matches_f64_reference; smaller results hit the
2⁻⁶⁴ absolute resolution).
self ≤ 0 or a negative exponent returns ZERO (no NaN; callers that
need those cases handle them explicitly).
§Integer part of the exponent
Exponents whose integer part is at most 64 use left-to-right repeated
multiplication; above 64 the integer part uses exponentiation by
squaring. The seam exists to keep results bit-identical to 1.5.0 and
earlier wherever those versions were already correct: fixed-point
multiplication truncates and is therefore not associative, so the two
orders differ in the low bits for a base that is not exactly
representable. Before 1.6.0 the integer part was silently capped at 64
(1.01^100 returned 1.01^64 = 1.89 rather than 2.705), so only the
n ≤ 64 range has bits worth preserving; above it the result was wrong
and the faster O(log n) order is used instead of an O(n) loop.
Sourcepub fn exp(self) -> Self
pub fn exp(self) -> Self
Deterministic e^self via 2^(x·log₂e) (Fix128::powf_pos with base 2;
negative arguments as 1 / e^|x|). Relative error ≲ 1e-6 for
|x| ≤ 40 (math::tests::exp_matches_f64_reference); x < −44 is
below the 2⁻⁶⁴ resolution and returns ZERO, and from x ≥ 63 · ln 2
(≈ 43.668, where e^x reaches 2⁶³) it saturates at the representable
maximum instead of wrapping.
Sourcepub fn ln(self) -> Self
pub fn ln(self) -> Self
Deterministic natural log via range reduction to [1, 2) + atanh
series (ln(m) = 2·atanh((m−1)/(m+1)), 16 terms). Non-positive input
returns ZERO (no panic, no NaN representation in Fix128). Moved
here from a private turbulence.rs helper of the same algorithm
(canonical source: one ln, not one per caller module).
Sourcepub fn checked_div(self, rhs: Self) -> Option<Self>
pub fn checked_div(self, rhs: Self) -> Option<Self>
Checked division: None when rhs == 0, otherwise Some(self / rhs).
The Div operator returns ZERO for a zero divisor (deterministic, no
panic, no NaN) which is convenient in solver hot paths but hides bugs
in caller code that never expected a zero — use this in validation and
setup paths. Bit-identical to / for every non-zero divisor.
Trait Implementations§
impl Copy for Fix128
Source§impl Div for Fix128
Fixed-point division, truncating toward zero on the raw bit pattern.
impl Div for Fix128
Fixed-point division, truncating toward zero on the raw bit pattern.
⚠️ Not the same direction as Mul, which floors toward
−∞. This operator takes the magnitudes apart, divides them and reapplies
the sign, so the result is symmetric about zero: (-a) / b == -(a / b)
exactly. Mul is not — (-a) * b is one raw unit below -(a * b)
whenever the product has a remainder. Measured on a = 1/3, b = 1/7.
The difference matters wherever the direction of the bias decides an
outcome rather than its size: a contraction ratio read as > 1 or = 1,
a residual that either reaches an exact fixed point or keeps drifting, a
quadrature weight that is one unit light on one side of zero and not the
other. Division is the safer of the two there, because the sign cannot
change which way the error leans.
§Division by zero
Returns ZERO (documented contract since 1.2.0; the behaviour predates
it). This keeps the solver deterministic and panic-free — a zero inverse
mass, a degenerate normal or a zero dt never poisons a lockstep peer with
NaN — but it also means a caller bug is silently masked. Use
Fix128::checked_div where a zero divisor is an error. A Result-based
operator is planned for 2.0 (PhysicsError is not #[non_exhaustive],
so a new variant cannot be added in 1.x).
impl Eq for Fix128
Source§impl Mul for Fix128
Fixed-point multiplication: the 256-bit product of the two Q64.64
values, keeping bits [192:64].
impl Mul for Fix128
Fixed-point multiplication: the 256-bit product of the two Q64.64
values, keeping bits [192:64].
§Overflow and rounding
- Wrapping: if the mathematical product does not fit the ±2^63
integer range the high bits are discarded — the result wraps modulo
2^128 of the raw two’s-complement representation. There is no
saturation and no panic, in debug builds too (every intermediate uses
wrapping_*). Callers that need range safety must check operands beforehand; the engine’s own hot paths keep magnitudes far below the limit. - Truncation: fractional bits below 2^-64 are dropped, which is a
floor toward −∞ on the two’s-complement bit pattern (e.g.
-2^-64 * 0.5 == -2^-64, not0).
Both properties are part of the determinism contract: the same operands yield the same bits on every platform.
Source§impl Ord for Fix128
impl Ord for Fix128
1.21.0 (const: unstable) · Source§fn max(self, other: Self) -> Selfwhere
Self: Sized,
fn max(self, other: Self) -> Selfwhere
Self: Sized,
1.21.0 (const: unstable) · Source§fn min(self, other: Self) -> Selfwhere
Self: Sized,
fn min(self, other: Self) -> Selfwhere
Self: Sized,
Source§impl PartialOrd for Fix128
impl PartialOrd for Fix128
impl StructuralPartialEq for Fix128
Auto Trait Implementations§
impl Freeze for Fix128
impl RefUnwindSafe for Fix128
impl Send for Fix128
impl Sync for Fix128
impl Unpin for Fix128
impl UnsafeUnpin for Fix128
impl UnwindSafe for Fix128
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more