llm_tool/types/truncation/
ratio.rs1use alloc::format;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct HeadRatio {
8 numerator: u32,
9 denominator: u32,
10}
11
12impl HeadRatio {
13 pub const BALANCED: Self = Self {
15 numerator: 1,
16 denominator: 2,
17 };
18 pub const HEAD_ONLY: Self = Self {
20 numerator: 1,
21 denominator: 1,
22 };
23 pub const TAIL_ONLY: Self = Self {
25 numerator: 0,
26 denominator: 1,
27 };
28
29 #[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 #[must_use]
48 pub const fn from_percent(percent: u32) -> Self {
49 Self::new(percent, 100)
50 }
51
52 #[must_use]
54 pub const fn from_permille(permille: u32) -> Self {
55 Self::new(permille, 1000)
56 }
57
58 #[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 #[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}