1use err_derive::Error;
10
11use std::convert::TryFrom;
12
13use std::result;
14
15use std::fmt;
16use std::fmt::{Display, Formatter};
17
18use std::cmp::Ordering;
19
20#[derive(Debug, Copy, Clone, PartialEq, Error)]
23pub enum Error {
24 #[error(display =
26 "Out of lower bound ±1.000 (= ±1 024 ^ 0) for number {:.3E}", _0)]
27 OutOfLowerBound(f64),
28 #[error(display =
31 "Out of upper bound ±1 023 Yi (≈ ±1 024 ^ 9) for number {:.3E}", _0)]
32 OutOfUpperBound(f64),
33 #[error(display = "Not a number (NaN)")]
35 Nan,
36}
37
38impl Eq for Error {}
39
40pub type Result<T> = result::Result<T, Error>;
42
43#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
55pub struct Signifix {
56 number: super::Signifix
57}
58
59pub const DEF_MIN_LEN: usize = 8;
61
62pub const DEF_MAX_LEN: usize = 9;
64
65pub const SYMBOLS: [Option<&str>; 9] = [
68 None,
69 Some("Ki"), Some("Mi"), Some("Gi"), Some("Ti"),
70 Some("Pi"), Some("Ei"), Some("Zi"), Some("Yi"),
71];
72
73pub const FACTORS: [f64; 9] = [
76 (1u128 << 00) as f64,
77 (1u128 << 10) as f64, (1u128 << 20) as f64,
78 (1u128 << 30) as f64, (1u128 << 40) as f64,
79 (1u128 << 50) as f64, (1u128 << 60) as f64,
80 (1u128 << 70) as f64, (1u128 << 80) as f64,
81];
82
83impl Signifix {
84 pub fn significand(&self) -> f64 {
86 self.number.significand()
87 }
88
89 pub fn numerator(&self) -> i32 {
91 self.number.numerator()
92 }
93
94 pub fn denominator(&self) -> i32 {
96 self.number.denominator()
97 }
98
99 pub fn exponent(&self) -> usize {
101 self.number.exponent()
102 }
103
104 pub fn integer(&self) -> i32 {
106 self.number.integer()
107 }
108
109 pub fn fractional(&self) -> i32 {
111 self.number.fractional()
112 }
113
114 pub fn parts(&self) -> (i32, i32) {
116 self.number.parts()
117 }
118
119 pub fn prefix(&self) -> usize {
122 self.number.prefix()
123 }
124
125 pub fn symbol(&self) -> Option<&str> {
127 SYMBOLS[self.prefix()]
128 }
129
130 pub fn factor(&self) -> f64 {
132 FACTORS[self.prefix()]
133 }
134
135 pub fn fmt(&self, f: &mut Formatter,
145 decimal_mark: &str, grouping_sep: &str)
146 -> fmt::Result {
147 debug_assert_eq!(decimal_mark.chars().count(), 1);
148 debug_assert_eq!(grouping_sep.chars().count(), 1);
149 let sign = if self.numerator().is_negative() { "-" } else
150 if f.sign_plus() { "+" } else { "" };
151 let symbol = self.symbol().unwrap_or(" ".into());
152 if self.exponent() == 0 {
153 f.pad(&format!("{}1{}{:03} {}",
154 sign, grouping_sep, self.numerator().abs() - 1_000, symbol))
155 } else {
156 let (integer, fractional) = self.parts();
157 f.pad(&format!("{}{}{}{:05$} {}",
158 sign, integer.abs(), decimal_mark, fractional, symbol,
159 self.exponent()))
160 }
161 }
162}
163
164impl Display for Signifix {
165 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
166 self.fmt(f, ".", " ")
167 }
168}
169
170try_from! { i8, i16, i32, i64, i128, isize }
171try_from! { u8, u16, u32, u64, u128, usize }
172
173try_from! { f32 }
174
175impl TryFrom<f64> for Signifix {
176 type Error = Error;
177
178 fn try_from(number: f64) -> Result<Self> {
179 let (numerator, prefix) = {
180 let number = number.abs();
181 let prefix = match FACTORS[1..].binary_search_by(|factor|
182 factor.partial_cmp(&number).unwrap_or(Ordering::Less)
183 ) { Ok(prefix) => prefix, Err(prefix) => prefix };
184 (number / FACTORS[prefix], prefix)
185 };
186 let scaled = |pow: f64| (numerator * pow).round();
187 let signed = |abs: f64| if number.is_sign_negative()
188 { -abs } else { abs };
189 let middle = scaled(1E+02);
190 if middle < 1E+04 {
191 let lower = scaled(1E+03);
192 if lower < 1E+04 {
193 if lower < 1E+03 {
194 Err(Error::OutOfLowerBound(number))
195 } else {
196 Ok(Self {
197 number: super::Signifix {
198 numerator: signed(lower) as i16,
199 exponent: 3,
200 prefix: prefix as u8,
201 }
202 })
203 }
204 } else {
205 Ok(Self {
206 number: super::Signifix {
207 numerator: signed(middle) as i16,
208 exponent: 2,
209 prefix: prefix as u8,
210 }
211 })
212 }
213 } else {
214 let upper = scaled(1E+01);
215 if upper < 1E+04 {
216 Ok(Self {
217 number: super::Signifix {
218 numerator: signed(upper) as i16,
219 exponent: 1,
220 prefix: prefix as u8,
221 }
222 })
223 } else {
224 let above = numerator.round();
225 if above < 1.024E+03 {
226 Ok(Self {
227 number: super::Signifix {
228 numerator: signed(above) as i16,
229 exponent: 0,
230 prefix: prefix as u8,
231 }
232 })
233 } else {
234 let prefix = prefix + 1;
235 if prefix < FACTORS.len() {
236 Ok(Self {
237 number: super::Signifix {
238 numerator: signed(1E+03) as i16,
239 exponent: 3,
240 prefix: prefix as u8,
241 }
242 })
243 } else {
244 if number.is_nan() {
245 Err(Error::Nan)
246 } else {
247 Err(Error::OutOfUpperBound(number))
248 }
249 }
250 }
251 }
252 }
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259 use std::f64;
260 use std::mem::size_of;
261
262 fn fmt(number: f64) -> Result<String> {
263 Signifix::try_from(number).map(|number| format!("{}", number))
264 }
265 fn pos(number: f64) -> Result<String> {
266 Signifix::try_from(number).map(|number| format!("{:+}", number))
267 }
268 fn pad(number: f64) -> Result<String> {
269 Signifix::try_from(number)
270 .map(|number| format!("{:>1$}", number, DEF_MAX_LEN))
271 }
272
273 #[test]
274 fn factors_to_symbols() {
275 assert_eq!(fmt(1_024f64.powi(0)), Ok("1.000 ".into()));
276 assert_eq!(fmt(1_024f64.powi(1)), Ok("1.000 Ki".into()));
277 assert_eq!(fmt(1_024f64.powi(2)), Ok("1.000 Mi".into()));
278 assert_eq!(fmt(1_024f64.powi(3)), Ok("1.000 Gi".into()));
279 assert_eq!(fmt(1_024f64.powi(4)), Ok("1.000 Ti".into()));
280 assert_eq!(fmt(1_024f64.powi(5)), Ok("1.000 Pi".into()));
281 assert_eq!(fmt(1_024f64.powi(6)), Ok("1.000 Ei".into()));
282 assert_eq!(fmt(1_024f64.powi(7)), Ok("1.000 Zi".into()));
283 assert_eq!(fmt(1_024f64.powi(8)), Ok("1.000 Yi".into()));
284 }
285 #[test]
286 fn fixed_significance() {
287 assert_eq!(fmt(1_024f64.powi(0) * 100.0f64), Ok("100.0 ".into()));
288 assert_eq!(fmt(1_024f64.powi(0) * 123.4f64), Ok("123.4 ".into()));
289 assert_eq!(fmt(1_024f64.powi(0) * 1_000f64), Ok("1 000 ".into()));
290 assert_eq!(fmt(1_024f64.powi(0) * 1_002f64), Ok("1 002 ".into()));
291 assert_eq!(fmt(1_024f64.powi(0) * 1_023f64), Ok("1 023 ".into()));
292 assert_eq!(fmt(1_024f64.powi(1) * 1.000f64), Ok("1.000 Ki".into()));
293 assert_eq!(fmt(1_024f64.powi(1) * 1.234f64), Ok("1.234 Ki".into()));
294 assert_eq!(fmt(1_024f64.powi(1) * 10.00f64), Ok("10.00 Ki".into()));
295 assert_eq!(fmt(1_024f64.powi(1) * 12.34f64), Ok("12.34 Ki".into()));
296 assert_eq!(fmt(1_024f64.powi(1) * 100.0f64), Ok("100.0 Ki".into()));
297 assert_eq!(fmt(1_024f64.powi(1) * 123.4f64), Ok("123.4 Ki".into()));
298 assert_eq!(fmt(1_024f64.powi(1) * 1_000f64), Ok("1 000 Ki".into()));
299 assert_eq!(fmt(1_024f64.powi(1) * 1_002f64), Ok("1 002 Ki".into()));
300 assert_eq!(fmt(1_024f64.powi(1) * 1_023f64), Ok("1 023 Ki".into()));
301 assert_eq!(fmt(1_024f64.powi(2) * 1.000f64), Ok("1.000 Mi".into()));
302 assert_eq!(fmt(1_024f64.powi(2) * 1.234f64), Ok("1.234 Mi".into()));
303 }
304 #[test]
305 fn formatting_options() {
306 assert_eq!(fmt(-1E+00), Ok("-1.000 ".into()));
307 assert_eq!(fmt( 1E+00), Ok( "1.000 ".into()));
308 assert_eq!(fmt(-1E+03), Ok("-1 000 ".into()));
309 assert_eq!(fmt( 1E+03), Ok( "1 000 ".into()));
310 assert_eq!(pos(-1E+00), Ok("-1.000 ".into()));
311 assert_eq!(pos( 1E+00), Ok("+1.000 ".into()));
312 assert_eq!(pos(-1E+03), Ok("-1 000 ".into()));
313 assert_eq!(pos( 1E+03), Ok("+1 000 ".into()));
314 assert_eq!(pad(-1E+00), Ok("-1.000 ".into()));
315 assert_eq!(pad( 1E+00), Ok(" 1.000 ".into()));
316 assert_eq!(pad(-1E+03), Ok("-1 000 ".into()));
317 assert_eq!(pad( 1E+03), Ok(" 1 000 ".into()));
318 }
319 #[test]
320 fn lower_prefix_bound() {
321 assert_eq!(fmt(-0.999_50E+00),
322 Ok("-1.000 ".into()));
323 assert_eq!(fmt(-0.999_49E+00),
324 Err(Error::OutOfLowerBound(-0.999_49E+00)));
325 }
326 #[test]
327 fn upper_prefix_bound() {
328 assert_eq!(fmt(-1_237.3E+24),
329 Ok("-1 023 Yi".into()));
330 assert_eq!(fmt(-1_237.4E+24),
331 Err(Error::OutOfUpperBound(-1_237.4E+24)));
332 }
333 #[test]
334 fn upper_prefix_round() {
335 assert_eq!(fmt(1_023.499_999_999_999_94E+00), Ok("1 023 ".into()));
336 assert_eq!(fmt(1_023.499_999_999_999_95E+00), Ok("1.000 Ki".into()));
337 }
338 #[test]
339 fn fp_category_safety() {
340 assert_eq!(fmt(0f64),
341 Err(Error::OutOfLowerBound(0f64)));
342 assert_eq!(fmt(f64::NEG_INFINITY),
343 Err(Error::OutOfUpperBound(f64::NEG_INFINITY)));
344 assert_eq!(fmt(f64::INFINITY),
345 Err(Error::OutOfUpperBound(f64::INFINITY)));
346 assert_eq!(fmt(f64::NAN),
347 Err(Error::Nan));
348 }
349 #[test]
350 fn ord_implementation() {
351 assert!(Signifix::try_from(1_024f64.powi(1)).unwrap()
352 < Signifix::try_from(1_024f64.powi(2)).unwrap());
353 assert!(Signifix::try_from(1E+01).unwrap()
354 < Signifix::try_from(1E+02).unwrap());
355 assert!(Signifix::try_from(1E+02).unwrap()
356 < Signifix::try_from(1E+03).unwrap());
357 assert!(Signifix::try_from(1E+02).unwrap()
358 < Signifix::try_from(2E+02).unwrap());
359 }
360 #[test]
361 fn mem_size_of_struct() {
362 assert_eq!(size_of::<Signifix>(), 4);
363 }
364}