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::value::Value;
161
162 #[test]
163 fn format_value_without_groupings() {
164 let x = 3.4e-12f32;
165 let v: Value = x.into();
166 let unit = "F"; let actual = format!("result is {}{u}", format_value!(v, "{:>8.2}"), u = unit);
169 let expected = "result is 3.40 pF";
170 assert_eq!(actual, expected);
171
172 let actual = format!("result is {}{u}", format_value!(v, "{:<8.3}"), u = unit);
173 let expected = "result is 3.400 pF";
174 assert_eq!(actual, expected);
175 }
176
177 #[test]
178 fn format_value_with_groupings() {
179 let x = 1234.5678;
180 let v: Value = x.into();
181 let unit = "m"; let actual = format!(
184 "result is {}{u}",
185 format_value!(v, "{:.7}", groupings: '_'),
186 u = unit
187 );
188 let expected = "result is 1.234_567_8 km";
189 assert_eq!(actual, expected);
190
191 use crate::base::Base;
192 use crate::prefix::Constraint;
193
194 let v = Value::new_with(x, Base::B1000, Constraint::UnitAndBelow);
195 let unit = "s";
196
197 let actual = format!(
198 "result is {}{u}",
199 format_value!(v, "{:.5}", groupings: '_'),
200 u = unit
201 );
202 let expected = "result is 1_234.567_80 s";
203 assert_eq!(actual, expected);
204 }
205
206 #[test]
207 fn separate_float() {
208 let actual: String = separated_float("123456.123456", '_');
209 let expected = "123_456.123_456";
210 assert_eq!(actual, expected);
211
212 let actual: String = separated_float("123456789.123456789", '_');
213 let expected = "123_456_789.123_456_789";
214 assert_eq!(actual, expected);
215
216 let actual: String = separated_float("1234567.1234567", '_');
217 let expected = "1_234_567.123_456_7";
218 assert_eq!(actual, expected);
219
220 let actual: String = separated_float("--1234567.1234567++", '_');
221 let expected = "--1_234_567.123_456_7++";
222 assert_eq!(actual, expected);
223 }
224
225 #[test]
226 fn int_part_with_separate_thousands_backward() {
227 let actual = separate_thousands_backward("123456", '_');
228 let expected = "123_456";
229 assert_eq!(actual, expected);
230
231 let actual = separate_thousands_backward(" 123456..", '_');
232 let expected = " 123_456..";
233 assert_eq!(actual, expected);
234 }
235
236 #[test]
237 fn frac_part_with_separate_thousands_forward() {
238 let actual = separate_thousands_forward(".123456789", '_');
239 let expected = ".123_456_789";
240 assert_eq!(actual, expected);
241
242 let actual = separate_thousands_forward(".1234567--", '_');
243 let expected = ".123_456_7--";
244 assert_eq!(actual, expected);
245 }
246
247 #[test]
248 fn format_zero_value() {
249 let x = 0.0f32;
250 let v: Value = x.into();
251 let unit = "F"; let actual = format!("result is {}{u}", format_value!(v, "{:>8.2}"), u = unit);
254 let expected = "result is 0.00 F";
255 assert_eq!(actual, expected);
256
257 let actual = format!("result is {}{u}", format_value!(v, "{:<8.3}"), u = unit);
258 let expected = "result is 0.000 F";
259 assert_eq!(actual, expected);
260 }
261}
262
263