1#[macro_export]
65macro_rules! format_value {
66 ($name:ident, $fmt_str:literal) => {
67 format_args! {
68 concat!($fmt_str, " {}{}"),
69 $name.mantissa,
70 $name.prefix,
71 match $name.base {
72 $crate::base::Base::B1000 => "",
73 $crate::base::Base::B1024 => if $name.prefix == $crate::prefix::Prefix::Unit {""} else {"i"},
74 },
75 }
76 };
77
78 ($name:ident, $fmt_str:literal, groupings: $separator:expr) => {
79 format_args! {
80 "{} {}{}",
81 $crate::format::separated_float(&format!($fmt_str, $name.mantissa), $separator),
82 $name.prefix,
83 match $name.base {
84 $crate::base::Base::B1000 => "",
85 $crate::base::Base::B1024 => if $name.prefix == $crate::prefix::Prefix::Unit {""} else {"i"},
86 },
87 }
88 };
89
90 ($name:ident, $fmt_str:literal, groupings: $separator:expr, no_unit) => {
91 format_args! {
92 "{}{}{}{}",
93 $crate::format::separated_float(&format!($fmt_str, $name.mantissa), $separator),
94 match $name.prefix {
95 $crate::prefix::Prefix::Unit => "",
96 _=> " "
97 },
98 $name.prefix,
99 match $name.base {
100 $crate::base::Base::B1000 => "",
101 $crate::base::Base::B1024 => if $name.prefix == $crate::prefix::Prefix::Unit {""} else {"i"},
102 },
103 }
104 };
105}
106
107pub fn separated_float(input: &str, separator: char) -> String {
112 let idx = match input.find('.') {
113 Some(i) => i,
114 None => input.len(),
115 };
116
117 let int_part = &input[..idx];
118 let frac_part = &input[idx..];
119
120 let int_part_separated = separate_thousands_backward(int_part, separator);
121 let frac_part_separated = separate_thousands_forward(frac_part, separator);
122 int_part_separated + &frac_part_separated
123}
124
125fn separate_thousands_backward(input: &str, separator: char) -> String {
126 let mut output = String::with_capacity(input.len() + input.len() / 4);
127 let mut pos = 0;
128 for ch in input.chars().rev() {
129 if ch.is_ascii_digit() {
130 if pos > 1 && pos % 3 == 0 {
132 output.push(separator);
133 }
134 pos += 1;
135 }
136 output.push(ch);
137 }
138 output.chars().rev().collect()
139}
140
141fn separate_thousands_forward(input: &str, separator: char) -> String {
142 let mut output = String::with_capacity(input.len() + input.len() / 4);
143 let mut pos = 0;
144 for ch in input.chars() {
145 if ch.is_ascii_digit() {
146 if pos > 1 && pos % 3 == 0 {
148 output.push(separator);
149 }
150 pos += 1;
151 }
152 output.push(ch);
153 }
154 output
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use crate::format_value;
161 use crate::value::Value;
162
163 #[test]
164 fn format_value_without_groupings() {
165 let x = 3.4e-12f32;
166 let v: Value = x.into();
167 let unit = "F"; let actual = format!("result is {}{u}", format_value!(v, "{:>8.2}"), u = unit);
170 let expected = "result is 3.40 pF";
171 assert_eq!(actual, expected);
172
173 let actual = format!("result is {}{u}", format_value!(v, "{:<8.3}"), u = unit);
174 let expected = "result is 3.400 pF";
175 assert_eq!(actual, expected);
176 }
177
178 #[test]
179 fn format_value_with_groupings() {
180 let x = 1234.5678;
181 let v: Value = x.into();
182 let unit = "m"; let actual = format!(
185 "result is {}{u}",
186 format_value!(v, "{:.7}", groupings: '_'),
187 u = unit
188 );
189 let expected = "result is 1.234_567_8 km";
190 assert_eq!(actual, expected);
191
192 use crate::base::Base;
193 use crate::prefix::Constraint;
194
195 let v = Value::new_with(x, Base::B1000, Constraint::UnitAndBelow);
196 let unit = "s";
197
198 let actual = format!(
199 "result is {}{u}",
200 format_value!(v, "{:.5}", groupings: '_'),
201 u = unit
202 );
203 let expected = "result is 1_234.567_80 s";
204 assert_eq!(actual, expected);
205 }
206
207 #[test]
208 fn separate_float() {
209 let actual: String = separated_float("123456.123456", '_');
210 let expected = "123_456.123_456";
211 assert_eq!(actual, expected);
212
213 let actual: String = separated_float("123456789.123456789", '_');
214 let expected = "123_456_789.123_456_789";
215 assert_eq!(actual, expected);
216
217 let actual: String = separated_float("1234567.1234567", '_');
218 let expected = "1_234_567.123_456_7";
219 assert_eq!(actual, expected);
220
221 let actual: String = separated_float("--1234567.1234567++", '_');
222 let expected = "--1_234_567.123_456_7++";
223 assert_eq!(actual, expected);
224 }
225
226 #[test]
227 fn int_part_with_separate_thousands_backward() {
228 let actual = separate_thousands_backward("123456", '_');
229 let expected = "123_456";
230 assert_eq!(actual, expected);
231
232 let actual = separate_thousands_backward(" 123456..", '_');
233 let expected = " 123_456..";
234 assert_eq!(actual, expected);
235 }
236
237 #[test]
238 fn frac_part_with_separate_thousands_forward() {
239 let actual = separate_thousands_forward(".123456789", '_');
240 let expected = ".123_456_789";
241 assert_eq!(actual, expected);
242
243 let actual = separate_thousands_forward(".1234567--", '_');
244 let expected = ".123_456_7--";
245 assert_eq!(actual, expected);
246 }
247
248 #[test]
249 fn format_zero_value() {
250 let x = 0.0f32;
251 let v: Value = x.into();
252 let unit = "F"; let actual = format!("result is {}{u}", format_value!(v, "{:>8.2}"), u = unit);
255 let expected = "result is 0.00 F";
256 assert_eq!(actual, expected);
257
258 let actual = format!("result is {}{u}", format_value!(v, "{:<8.3}"), u = unit);
259 let expected = "result is 0.000 F";
260 assert_eq!(actual, expected);
261 }
262}
263
264