1pub(crate) const MAX_DECIMALS: usize = 400;
9
10pub(crate) fn decimal_digits(a: f64) -> ([u8; 15], i32) {
18 let mut digits = [0u8; 15];
19 if !a.is_finite() || a <= 0.0 {
20 return (digits, 0);
21 }
22 let (d16, exp) = significant_digits(a, 16);
25 for (slot, d) in digits.iter_mut().zip(&d16) {
26 *slot = *d;
27 }
28 match d16.get(15).copied().unwrap_or(0) {
29 0..=4 => (digits, exp),
30 5 if is_exact_tie(a) => (digits, exp),
31 5 => {
32 let (d15, exp15) = significant_digits(a, 15);
34 let mut out = [0u8; 15];
35 for (slot, d) in out.iter_mut().zip(&d15) {
36 *slot = *d;
37 }
38 (out, exp15)
39 }
40 _ => {
41 let mut exp = exp;
42 let mut carry = true;
43 for d in digits.iter_mut().rev() {
44 if *d == 9 {
45 *d = 0;
46 } else {
47 *d += 1;
48 carry = false;
49 break;
50 }
51 }
52 if carry {
53 digits = [0; 15];
54 digits[0] = 1;
55 exp += 1;
56 }
57 (digits, exp)
58 }
59 }
60}
61
62fn significant_digits(a: f64, n: usize) -> (Vec<u8>, i32) {
65 let s = format!("{:.*e}", n.saturating_sub(1), a);
66 let (mant, exp) = s.split_once('e').unwrap_or((s.as_str(), "0"));
67 (mant.bytes().filter(u8::is_ascii_digit).map(|b| b - b'0').collect(), exp.parse().unwrap_or(0))
68}
69
70fn is_exact_tie(a: f64) -> bool {
72 let is_tie = |ds: &[u8]| ds.get(15) == Some(&5) && ds.get(16..).is_some_and(|t| t.iter().all(|&d| d == 0));
73 is_tie(&significant_digits(a, 30).0) && is_tie(&significant_digits(a, 800).0)
76}
77
78pub(crate) fn fixed_from_digits(digits: &[u8; 15], exp: i32, decimals: usize) -> (String, String) {
82 let decimals_calc = decimals.min(MAX_DECIMALS);
83 let keep = exp as i64 + decimals_calc as i64 + 1;
84 let mut m: Vec<u8> = if keep <= 0 {
85 if keep == 0 && digits.first().copied().unwrap_or(0) >= 5 { vec![1] } else { Vec::new() }
86 } else if keep as usize >= digits.len() {
87 let mut v = digits.to_vec();
88 v.resize(keep as usize, 0);
89 v
90 } else {
91 let keep = keep as usize;
92 let mut v = digits.get(..keep).map(<[u8]>::to_vec).unwrap_or_default();
93 if digits.get(keep).copied().unwrap_or(0) >= 5 {
94 let mut carry = true;
95 for d in v.iter_mut().rev() {
96 if *d == 9 {
97 *d = 0;
98 } else {
99 *d += 1;
100 carry = false;
101 break;
102 }
103 }
104 if carry {
105 v.insert(0, 1);
106 }
107 }
108 v
109 };
110 let lead = m.iter().take_while(|&&d| d == 0).count();
111 m.drain(..lead);
112 let to_s = |ds: &[u8]| ds.iter().map(|d| char::from(b'0' + d)).collect::<String>();
113 let (int, mut frac) = if m.len() <= decimals_calc {
114 let mut f = "0".repeat(decimals_calc - m.len());
115 f.push_str(&to_s(&m));
116 (String::new(), f)
117 } else {
118 let split = m.len() - decimals_calc;
119 (to_s(m.get(..split).unwrap_or(&[])), to_s(m.get(split..).unwrap_or(&[])))
120 };
121 if decimals > decimals_calc {
122 frac.push_str(&"0".repeat(decimals - decimals_calc));
123 }
124 (int, frac)
125}
126
127pub(crate) fn fixed(a: f64, decimals: usize) -> (String, String) {
129 if !a.is_finite() || a <= 0.0 {
130 return (String::new(), "0".repeat(decimals.min(MAX_DECIMALS * 4)));
131 }
132 let (d, e) = decimal_digits(a);
133 fixed_from_digits(&d, e, decimals)
134}
135
136pub fn format_general_fit(n: f64, max_chars: usize) -> Option<String> {
139 let max_chars = max_chars.min(1024);
142 if !n.is_finite() {
143 return None;
144 }
145 if n == 0.0 {
146 return (max_chars >= 1).then(|| "0".to_string());
147 }
148 let sign = if n < 0.0 { "-" } else { "" };
149 let (digits, x) = decimal_digits(n.abs());
150 let int_len = sign.len() + (x.max(0) as usize) + 1;
151 if x >= -4 && int_len <= max_chars {
153 let avail = max_chars - int_len;
154 let mut decimals = if avail >= 2 { avail - 1 } else { 0 };
155 loop {
156 let (i, f) = fixed_from_digits(&digits, x, decimals);
157 let f = f.trim_end_matches('0');
158 if i.is_empty() && f.is_empty() {
159 break; }
161 let mut s = String::from(sign);
162 s.push_str(if i.is_empty() { "0" } else { &i });
163 if !f.is_empty() {
164 s.push('.');
165 s.push_str(f);
166 }
167 if s.chars().count() <= max_chars {
168 return Some(s);
169 }
170 if decimals == 0 {
171 break;
172 }
173 decimals -= 1;
174 }
175 }
176 for md in (0..=max_chars.min(14)).rev() {
177 let mut e = x;
178 let (mut mi, mut mf) = fixed_from_digits(&digits, 0, md);
179 if mi.len() > 1 {
180 e += 1;
181 (mi, mf) = fixed_from_digits(&digits, -1, md);
182 }
183 let mf = mf.trim_end_matches('0');
184 let mut s = String::from(sign);
185 s.push_str(if mi.is_empty() { "0" } else { &mi });
186 if !mf.is_empty() {
187 s.push('.');
188 s.push_str(mf);
189 }
190 s.push_str(&format!("E{}{:02}", if e < 0 { '-' } else { '+' }, e.unsigned_abs()));
191 if s.chars().count() <= max_chars {
192 return Some(s);
193 }
194 }
195 None
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn rounding() {
204 assert_eq!(fixed(2.675, 2), ("2".into(), "68".into()));
205 assert_eq!(fixed(1.005, 2), ("1".into(), "01".into()));
206 assert_eq!(fixed(0.5, 0), ("1".into(), "".into()));
207 assert_eq!(fixed(0.04, 1), ("".into(), "0".into()));
208 assert_eq!(fixed(0.05, 1), ("".into(), "1".into()));
209 assert_eq!(fixed(9.999, 2), ("10".into(), "00".into()));
210 assert_eq!(fixed(0.0004, 3), ("".into(), "000".into()));
211 assert_eq!(fixed(0.0005, 3), ("".into(), "001".into()));
212 assert_eq!(fixed(1e20, 0).0, "100000000000000000000");
213 assert_eq!(fixed(0.0, 2), ("".into(), "00".into()));
214 }
215
216 #[test]
217 fn general_fit() {
218 assert_eq!(format_general_fit(1e11, 11).as_deref(), Some("1E+11"));
219 assert_eq!(format_general_fit(12345678901.0, 11).as_deref(), Some("12345678901"));
220 assert_eq!(format_general_fit(1.5, usize::MAX).as_deref(), Some("1.5"));
222 assert_eq!(format_general_fit(123456789012.0, 11).as_deref(), Some("1.23457E+11"));
223 assert_eq!(format_general_fit(0.1 + 0.2, 11).as_deref(), Some("0.3"));
224 assert_eq!(format_general_fit(1.0 / 3.0, 11).as_deref(), Some("0.333333333"));
225 assert_eq!(format_general_fit(123456.789012, 11).as_deref(), Some("123456.789"));
226 assert_eq!(format_general_fit(1e-10, 11).as_deref(), Some("1E-10"));
227 assert_eq!(format_general_fit(0.0000123456789, 11).as_deref(), Some("1.23457E-05"));
228 assert_eq!(format_general_fit(0.0001, 11).as_deref(), Some("0.0001"));
229 assert_eq!(format_general_fit(-5.0, 11).as_deref(), Some("-5"));
230 assert_eq!(format_general_fit(0.0, 11).as_deref(), Some("0"));
231 assert_eq!(format_general_fit(1234.5678, 6).as_deref(), Some("1234.6"));
232 assert_eq!(format_general_fit(1234.5678, 4).as_deref(), Some("1235"));
233 assert_eq!(format_general_fit(123456.0, 5).as_deref(), Some("1E+05"));
234 assert_eq!(format_general_fit(123456.0, 3), None);
235 assert_eq!(format_general_fit(9.99999999999, 5).as_deref(), Some("10"));
236 assert_eq!(format_general_fit(f64::NAN, 11), None);
237 assert_eq!(format_general_fit(1.7976931348623157e308, 11).as_deref(), Some("1.7977E+308"));
238 }
239}