Skip to main content

signifix/
binary.rs

1// Copyright (c) 2016-2019 Rouven Spreckels <n3vu0r@qu1x.org>
2//
3// Usage of the works is permitted provided that
4// this instrument is retained with the works, so that
5// any entity that uses the works is notified of this instrument.
6//
7// DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
8
9use 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/// An error arising from this module's `TryFrom` trait implementation for its
21/// `Signifix` type.
22#[derive(Debug, Copy, Clone, PartialEq, Error)]
23pub enum Error {
24	/// The given number is below the lower bound `±1.000` (`= ±1 024 ^ 0`).
25	#[error(display =
26		"Out of lower bound ±1.000 (= ±1 024 ^ 0) for number {:.3E}", _0)]
27	OutOfLowerBound(f64),
28	/// The given number is above the upper bound `±1 023 Yi` (`≈ ±1 024 ^ 9`)
29	/// of the uppermost binary prefix yobi (`Yi = 1 024 ^ 8`).
30	#[error(display =
31		"Out of upper bound ±1 023 Yi (≈ ±1 024 ^ 9) for number {:.3E}", _0)]
32	OutOfUpperBound(f64),
33	/// The given number is actually not a number (NaN).
34	#[error(display = "Not a number (NaN)")]
35	Nan,
36}
37
38impl Eq for Error {}
39
40/// The canonical `Result` type using this module's `Error` type.
41pub type Result<T> = result::Result<T, Error>;
42
43/// Intermediate implementor type of this module's `TryFrom` and `Display` trait
44/// implementations. Former tries to convert a given number into this type by
45/// determining the appropriate binary prefix, the normalized significand, and
46/// the decimal mark position while latter uses this type's fields to format the
47/// number as a string of four significant figures inclusive the binary prefix
48/// symbol.
49///
50/// Interpreted formatting parameters are
51///
52///   * `+` to prefix positive numbers with a plus sign,
53///   * `fill`, `alignment`, and `width` to pad or align numbers.
54#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
55pub struct Signifix {
56	number: super::Signifix
57}
58
59/// Number of characters in default notation when no sign is prefixed.
60pub const DEF_MIN_LEN: usize = 8;
61
62/// Number of characters in default notation when a sign is prefixed.
63pub const DEF_MAX_LEN: usize = 9;
64
65/// Binary prefix symbols from `Some("Ki")` to `Some("Yi")` indexed from `1` to
66/// `8`, or `None` indexed at `0`.
67pub 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
73/// Binary prefix factors from `1 024 ^ 1` to `1 024 ^ 8` indexed from `1` to
74/// `8`, or `1 024 ^ 0` indexed at `0`.
75pub 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	/// Signed significand normalized from `±1.000` over `±999.9` to `±1 023`.
85	pub fn significand(&self) -> f64 {
86		self.number.significand()
87	}
88
89	/// Signed significand numerator from `±1 000` to `±9 999`.
90	pub fn numerator(&self) -> i32 {
91		self.number.numerator()
92	}
93
94	/// Significand denominator of either `1`, `10`, `100`, or `1 000`.
95	pub fn denominator(&self) -> i32 {
96		self.number.denominator()
97	}
98
99	/// Exponent of significand denominator of either `0`, `1`, `2`, or `3`.
100	pub fn exponent(&self) -> usize {
101		self.number.exponent()
102	}
103
104	/// Signed integer part of significand from `±1` to `±1 023`.
105	pub fn integer(&self) -> i32 {
106		self.number.integer()
107	}
108
109	/// Fractional part of significand from `0` to `999`.
110	pub fn fractional(&self) -> i32 {
111		self.number.fractional()
112	}
113
114	/// Signed integer and fractional part at once, in given order.
115	pub fn parts(&self) -> (i32, i32) {
116		self.number.parts()
117	}
118
119	/// Binary prefix as `NAMES`, `SYMBOLS`, and `FACTORS` array index from `0`
120	/// to `8`.
121	pub fn prefix(&self) -> usize {
122		self.number.prefix()
123	}
124
125	/// Symbol of binary prefix from `Some("Ki")` to `Some("Yi")`, or `None`.
126	pub fn symbol(&self) -> Option<&str> {
127		SYMBOLS[self.prefix()]
128	}
129
130	/// Factor of binary prefix from `1 024 ^ 1` to `1 024 ^ 8`, or `1 024 ^ 0`.
131	pub fn factor(&self) -> f64 {
132		FACTORS[self.prefix()]
133	}
134
135	/// Format trait implementation allowing explicit localization.
136	///
137	/// Until there is a recommended and possibly implicit localization system
138	/// for Rust, explicit localization can be achieved by wrapping the
139	/// `Signifix` type into a locale-sensitive newtype which implements the
140	/// `Display` trait via this method. Used by this type's `Display` trait
141	/// implementation with a decimal point as `decimal_mark` and a whitespace
142	/// as `grouping_sep`. Both the `decimal_mark` and `grouping_sep` must be of
143	/// a single character.
144	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}