Skip to main content

llm_tool/types/truncation/
ratio.rs

1//! Head/tail budget allocation ratio.
2
3use alloc::format;
4
5/// Ratio of available content budget assigned to the head portion.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct HeadRatio {
8    numerator: u32,
9    denominator: u32,
10}
11
12impl HeadRatio {
13    /// 50/50 balance between head and tail.
14    pub const BALANCED: Self = Self {
15        numerator: 1,
16        denominator: 2,
17    };
18    /// All content budget allocated to the head.
19    pub const HEAD_ONLY: Self = Self {
20        numerator: 1,
21        denominator: 1,
22    };
23    /// All content budget allocated to the tail.
24    pub const TAIL_ONLY: Self = Self {
25        numerator: 0,
26        denominator: 1,
27    };
28
29    /// Construct a ratio from a numerator and denominator.
30    #[must_use]
31    pub const fn new(numerator: u32, denominator: u32) -> Self {
32        if denominator == 0 {
33            Self::BALANCED
34        } else {
35            Self {
36                numerator: if numerator > denominator {
37                    denominator
38                } else {
39                    numerator
40                },
41                denominator,
42            }
43        }
44    }
45
46    /// Construct a ratio from a percentage (0..=100).
47    #[must_use]
48    pub const fn from_percent(percent: u32) -> Self {
49        Self::new(percent, 100)
50    }
51
52    /// Construct a ratio from parts per thousand (0..=1000).
53    #[must_use]
54    pub const fn from_permille(permille: u32) -> Self {
55        Self::new(permille, 1000)
56    }
57
58    /// Construct a ratio from a floating point value in `[0.0, 1.0]`.
59    #[must_use]
60    pub fn from_f64(ratio: f64) -> Self {
61        if ratio.is_nan() || ratio <= 0.0 {
62            Self::TAIL_ONLY
63        } else if ratio >= 1.0 {
64            Self::HEAD_ONLY
65        } else {
66            let clamped = ratio.clamp(0.0, 1.0);
67            let s = format!("{clamped:.4}");
68            match s.strip_prefix("0.") {
69                Some(dec) => match dec.parse::<u32>() {
70                    Ok(val) => Self::new(val, 10_000),
71                    Err(e) => {
72                        tracing::debug!(error = %e, "failed to parse head ratio digits");
73                        Self::BALANCED
74                    }
75                },
76                None => Self::BALANCED,
77            }
78        }
79    }
80
81    /// Compute the portion of `total_budget` assigned to the head.
82    #[must_use]
83    pub fn compute_head_budget(&self, total_budget: usize) -> usize {
84        if self.denominator == 0 {
85            return total_budget / 2;
86        }
87        let total = u128::try_from(total_budget).unwrap_or(u128::MAX);
88        let num = u128::from(self.numerator);
89        let den = u128::from(self.denominator);
90        let head = (total.saturating_mul(num)) / den;
91        usize::try_from(head)
92            .unwrap_or(total_budget)
93            .min(total_budget)
94    }
95}
96
97impl Default for HeadRatio {
98    fn default() -> Self {
99        Self::BALANCED
100    }
101}
102
103impl From<f64> for HeadRatio {
104    fn from(r: f64) -> Self {
105        Self::from_f64(r)
106    }
107}