sidereon_core/astro/math/interp.rs
1//! Scalar linear-interpolation primitives with pinned operation order.
2//!
3//! GNSS interpolators are bit-exact against deployed references, so the two
4//! distinct evaluation orders below are kept as separate named helpers rather
5//! than folded into one: callers that precompute a fraction need
6//! divide-before-multiply, while clock interpolators pin multiply-before-divide.
7
8/// Linear interpolation by a precomputed fraction: `a + (b - a) * t`.
9///
10/// `t` is the interpolation parameter (`0.0` returns `a`, `1.0` returns `b`).
11/// The caller is responsible for forming `t`; this helper fixes only the
12/// `a + (b - a) * t` evaluation order.
13#[inline]
14pub fn lerp(a: f64, b: f64, t: f64) -> f64 {
15 a + (b - a) * t
16}
17
18/// Linear interpolation by an explicit ratio, multiplying before dividing:
19/// `a + (b - a) * num / den`.
20///
21/// This is `lerp` with `t = num / den` but with the division applied last, which
22/// is a different floating-point rounding than `lerp(a, b, num / den)`. The
23/// clock interpolators pin this order for bit-exact parity, so it has its own
24/// helper. The caller guarantees `den` is nonzero.
25#[inline]
26pub fn lerp_ratio(a: f64, b: f64, num: f64, den: f64) -> f64 {
27 a + (b - a) * num / den
28}
29
30#[cfg(test)]
31mod tests {
32 use super::*;
33
34 #[test]
35 fn lerp_matches_explicit_recipe_bits() {
36 let (a, b, t) = (1.25_f64, -3.5_f64, 0.3_f64);
37 assert_eq!(lerp(a, b, t).to_bits(), (a + (b - a) * t).to_bits());
38 }
39
40 #[test]
41 fn lerp_endpoints_are_exact() {
42 assert_eq!(lerp(2.0, 5.0, 0.0).to_bits(), 2.0_f64.to_bits());
43 assert_eq!(lerp(2.0, 5.0, 1.0).to_bits(), 5.0_f64.to_bits());
44 }
45
46 #[test]
47 fn lerp_ratio_matches_explicit_recipe_bits() {
48 let (a, b, num, den) = (1.0e-6_f64, 1.3e-6_f64, 7.0_f64, 30.0_f64);
49 assert_eq!(
50 lerp_ratio(a, b, num, den).to_bits(),
51 (a + (b - a) * num / den).to_bits()
52 );
53 }
54
55 #[test]
56 fn lerp_ratio_preserves_multiply_before_divide_order() {
57 // A case where multiply-before-divide and divide-before-multiply round
58 // differently, so the helper is pinned to the former.
59 let (a, b, num, den) = (0.0_f64, 1.0_f64, 1.0_f64, 3.0_f64);
60 assert_eq!(
61 lerp_ratio(a, b, num, den).to_bits(),
62 (a + (b - a) * num / den).to_bits()
63 );
64 }
65}