serpent_serializer/
numfmt.rs1struct Spec {
11 precision: usize,
12 conv: Conv,
13 upper: bool,
16}
17
18enum Conv {
19 G,
20 E,
21}
22
23fn parse(fmt: &str) -> Option<Spec> {
28 let b = fmt.as_bytes();
29 if b.first() != Some(&b'%') {
30 return None;
31 }
32 let mut i = 1;
33 while i < b.len() && b[i].is_ascii_digit() {
36 i += 1;
37 }
38 let mut precision = 6;
39 if i < b.len() && b[i] == b'.' {
40 i += 1;
41 let start = i;
42 while i < b.len() && b[i].is_ascii_digit() {
43 i += 1;
44 }
45 precision = fmt[start..i].parse().unwrap_or(0);
46 }
47 let (conv, upper) = match b.get(i) {
48 Some(&b'g') => (Conv::G, false),
49 Some(&b'G') => (Conv::G, true),
50 Some(&b'e') => (Conv::E, false),
51 Some(&b'E') => (Conv::E, true),
52 _ => return None,
53 };
54 if i + 1 != b.len() {
55 return None;
56 }
57 Some(Spec {
58 precision,
59 conv,
60 upper,
61 })
62}
63
64#[must_use]
70pub fn format(fmt: &str, x: f64) -> String {
71 let spec = parse(fmt).unwrap_or(Spec {
72 precision: 17,
73 conv: Conv::G,
74 upper: false,
75 });
76 if x.is_nan() {
77 return if spec.upper { "NAN" } else { "nan" }.to_string();
78 }
79 if x.is_infinite() {
80 let word = if spec.upper { "INF" } else { "inf" };
81 return if x > 0.0 {
82 word.to_string()
83 } else {
84 format!("-{word}")
85 };
86 }
87 let out = match spec.conv {
88 Conv::G => fmt_g(x, spec.precision),
89 Conv::E => fmt_e(x, spec.precision),
90 };
91 if spec.upper {
92 out.to_uppercase()
93 } else {
94 out
95 }
96}
97
98fn fmt_e(x: f64, precision: usize) -> String {
101 if x == 0.0 {
102 let mantissa = if precision == 0 {
103 "0".to_string()
104 } else {
105 format!("0.{}", "0".repeat(precision))
106 };
107 let sign = if x.is_sign_negative() { "-" } else { "" };
108 return format!("{sign}{mantissa}e+00");
109 }
110 let neg = x < 0.0;
111 let (digits, exp) = round_digits(x.abs(), precision + 1);
112 let mut out = String::new();
113 if neg {
114 out.push('-');
115 }
116 out.push(digits.as_bytes()[0] as char);
117 if precision > 0 {
118 out.push('.');
119 out.push_str(&digits[1..=precision]);
120 }
121 out.push('e');
122 if exp >= 0 {
123 out.push('+');
124 } else {
125 out.push('-');
126 }
127 let e = exp.abs();
128 if e < 10 {
129 out.push('0');
130 }
131 out.push_str(&e.to_string());
132 out
133}
134
135fn fmt_g(x: f64, precision: usize) -> String {
138 let p = if precision == 0 { 1 } else { precision };
139 if x == 0.0 {
140 return if x.is_sign_negative() {
141 "-0".to_string()
142 } else {
143 "0".to_string()
144 };
145 }
146 let neg = x < 0.0;
147 let (digits, exp) = round_digits(x.abs(), p);
148 let mut out = String::new();
150 if neg {
151 out.push('-');
152 }
153 if exp < -4 || exp >= p as i32 {
154 let mut mant = String::new();
156 mant.push(digits.as_bytes()[0] as char);
157 let frac = strip_zeros(&digits[1..]);
158 if !frac.is_empty() {
159 mant.push('.');
160 mant.push_str(&frac);
161 }
162 out.push_str(&mant);
163 out.push('e');
164 if exp >= 0 {
165 out.push('+');
166 } else {
167 out.push('-');
168 }
169 let e = exp.abs();
170 if e < 10 {
171 out.push('0');
172 }
173 out.push_str(&e.to_string());
174 } else if exp >= 0 {
175 let int_len = (exp + 1) as usize;
177 if digits.len() <= int_len {
178 let mut s = digits.clone();
179 s.push_str(&"0".repeat(int_len - digits.len()));
180 out.push_str(&s);
181 } else {
182 let (int_part, frac_part) = digits.split_at(int_len);
183 let frac = strip_zeros(frac_part);
184 out.push_str(int_part);
185 if !frac.is_empty() {
186 out.push('.');
187 out.push_str(&frac);
188 }
189 }
190 } else {
191 let zeros = (-exp - 1) as usize;
193 let frac = strip_zeros(&format!("{}{}", "0".repeat(zeros), digits));
194 out.push_str("0.");
195 out.push_str(&frac);
196 }
197 out
198}
199
200fn strip_zeros(s: &str) -> String {
201 s.trim_end_matches('0').to_string()
202}
203
204fn round_digits(x: f64, sig: usize) -> (String, i32) {
210 let s = format!("{x:.*e}", sig - 1);
213 let (mant, exp_str) = s.split_once('e').expect("scientific form has e");
214 let exp: i32 = exp_str.parse().expect("exponent parses");
215 let mut digits: Vec<u8> = mant.bytes().filter(|b| b.is_ascii_digit()).collect();
216 while digits.len() < sig {
217 digits.push(b'0');
218 }
219 digits.truncate(sig);
220 (String::from_utf8(digits).expect("ascii digits"), exp)
221}