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            s.strip_prefix("0.")
69                .map_or(Self::BALANCED, |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        }
77    }
78
79    /// Compute the portion of `total_budget` assigned to the head.
80    #[must_use]
81    pub fn compute_head_budget(&self, total_budget: usize) -> usize {
82        if self.denominator == 0 {
83            return total_budget / 2;
84        }
85        let total = u128::try_from(total_budget).unwrap_or(u128::MAX);
86        let num = u128::from(self.numerator);
87        let den = u128::from(self.denominator);
88        let head = (total.saturating_mul(num)) / den;
89        usize::try_from(head)
90            .unwrap_or(total_budget)
91            .min(total_budget)
92    }
93}
94
95impl Default for HeadRatio {
96    fn default() -> Self {
97        Self::BALANCED
98    }
99}
100
101impl From<f64> for HeadRatio {
102    fn from(r: f64) -> Self {
103        Self::from_f64(r)
104    }
105}