Skip to main content

polydat_core/numeric/
round_numbers.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! The round-number families: the powers, Fibonacci steps, and
5//! nearest-pick the `floor_*`, `ceiling_*`, and `closest_*` nodes and
6//! their native lowerings compute with.
7
8// ---------------------------------------------------------------------------
9// Private helpers (not nodes — plain module fns callable from node bodies).
10// ---------------------------------------------------------------------------
11
12/// True only for a strictly-positive, finite `x`. All family selectors gate
13/// on this and return `0.0` otherwise, so `x <= 0`, `NaN`, and `±inf` all
14/// fold to the zero magnitude without special-casing each node.
15#[inline]
16pub fn positive_finite(x: f64) -> bool {
17    x.is_finite() && x > 0.0
18}
19
20/// Largest power of ten `10^n <= x`. Assumes `positive_finite(x)`.
21///
22/// The exponent is taken from `log10(x).floor()` but reconstructed with
23/// `powi` (exact for `|result| < 2^53`) and corrected by at most one step,
24/// so a one-ulp `log10` error at an exact power-of-ten boundary can't leak.
25///
26/// `pub` so non-node callers can reuse the exact `floor_base10` formula
27/// without re-deriving it (DRY). `floor_base10` the node is just this
28/// function behind the `positive_finite` guard, so a caller that already
29/// guarantees a strictly-positive, finite `x` (e.g. the CQL batch-stride
30/// planner, which floors `budget / row_size`) can call this directly.
31#[inline]
32pub fn floor_pow10(x: f64) -> f64 {
33    let mut e = x.log10().floor() as i32;
34    let mut p = 10f64.powi(e);
35    if p > x {
36        e -= 1;
37        p = 10f64.powi(e);
38    } else if p * 10.0 <= x {
39        e += 1;
40        p = 10f64.powi(e);
41    }
42    p
43}
44
45/// Largest power of two `2^n <= x`. Assumes `positive_finite(x)`.
46///
47/// Same `powi` + one-step-correction scheme as [`floor_pow10`].
48#[inline]
49pub fn floor_pow2(x: f64) -> f64 {
50    let mut e = x.log2().floor() as i32;
51    let mut p = 2f64.powi(e);
52    if p > x {
53        e -= 1;
54        p = 2f64.powi(e);
55    } else if p * 2.0 <= x {
56        e += 1;
57        p = 2f64.powi(e);
58    }
59    p
60}
61
62/// Pick whichever of `lo` / `hi` is nearer to `x` by absolute distance.
63/// Ties resolve to `lo` (the floor), per the `closest_*` contract.
64#[inline]
65pub fn pick_closest(x: f64, lo: f64, hi: f64) -> f64 {
66    if (hi - x).abs() < (x - lo).abs() {
67        hi
68    } else {
69        lo
70    }
71}
72
73/// Largest Fibonacci number (`1, 2, 3, 5, 8, …`) that is `<= x`, or `0.0`
74/// when `x < 1` (nothing in the sequence is that small). Assumes finite `x`.
75pub fn floor_fibonacci_val(x: f64) -> f64 {
76    if x < 1.0 {
77        return 0.0;
78    }
79    let (mut a, mut b) = (1.0f64, 2.0f64);
80    while b <= x {
81        let next = a + b;
82        a = b;
83        b = next;
84        if !b.is_finite() {
85            return a;
86        }
87    }
88    a
89}
90
91/// Smallest Fibonacci number (`1, 2, 3, 5, 8, …`) that is `>= x`; `1.0` for
92/// `x <= 1`. Assumes `positive_finite(x)`.
93pub fn ceiling_fibonacci_val(x: f64) -> f64 {
94    let (mut a, mut b) = (1.0f64, 2.0f64);
95    if x <= a {
96        return a;
97    }
98    loop {
99        if b >= x {
100            return b;
101        }
102        let next = a + b;
103        a = b;
104        b = next;
105        if !b.is_finite() {
106            return b;
107        }
108    }
109}