use err_derive::Error;
use std::convert::TryFrom;
use std::result;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::cmp::Ordering;
#[derive(Debug, Copy, Clone, PartialEq, Error)]
pub enum Error {
#[error(display =
"Out of lower bound ±1.000 (= ±1 024 ^ 0) for number {:.3E}", _0)]
OutOfLowerBound(f64),
#[error(display =
"Out of upper bound ±1 023 Yi (≈ ±1 024 ^ 9) for number {:.3E}", _0)]
OutOfUpperBound(f64),
#[error(display = "Not a number (NaN)")]
Nan,
}
impl Eq for Error {}
pub type Result<T> = result::Result<T, Error>;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Signifix {
number: super::Signifix
}
pub const DEF_MIN_LEN: usize = 8;
pub const DEF_MAX_LEN: usize = 9;
pub const SYMBOLS: [Option<&str>; 9] = [
None,
Some("Ki"), Some("Mi"), Some("Gi"), Some("Ti"),
Some("Pi"), Some("Ei"), Some("Zi"), Some("Yi"),
];
pub const FACTORS: [f64; 9] = [
(1u128 << 00) as f64,
(1u128 << 10) as f64, (1u128 << 20) as f64,
(1u128 << 30) as f64, (1u128 << 40) as f64,
(1u128 << 50) as f64, (1u128 << 60) as f64,
(1u128 << 70) as f64, (1u128 << 80) as f64,
];
impl Signifix {
pub fn significand(&self) -> f64 {
self.number.significand()
}
pub fn numerator(&self) -> i32 {
self.number.numerator()
}
pub fn denominator(&self) -> i32 {
self.number.denominator()
}
pub fn exponent(&self) -> usize {
self.number.exponent()
}
pub fn integer(&self) -> i32 {
self.number.integer()
}
pub fn fractional(&self) -> i32 {
self.number.fractional()
}
pub fn parts(&self) -> (i32, i32) {
self.number.parts()
}
pub fn prefix(&self) -> usize {
self.number.prefix()
}
pub fn symbol(&self) -> Option<&str> {
SYMBOLS[self.prefix()]
}
pub fn factor(&self) -> f64 {
FACTORS[self.prefix()]
}
pub fn fmt(&self, f: &mut Formatter,
decimal_mark: &str, grouping_sep: &str)
-> fmt::Result {
debug_assert_eq!(decimal_mark.chars().count(), 1);
debug_assert_eq!(grouping_sep.chars().count(), 1);
let sign = if self.numerator().is_negative() { "-" } else
if f.sign_plus() { "+" } else { "" };
let symbol = self.symbol().unwrap_or(" ".into());
if self.exponent() == 0 {
f.pad(&format!("{}1{}{:03} {}",
sign, grouping_sep, self.numerator().abs() - 1_000, symbol))
} else {
let (integer, fractional) = self.parts();
f.pad(&format!("{}{}{}{:05$} {}",
sign, integer.abs(), decimal_mark, fractional, symbol,
self.exponent()))
}
}
}
impl Display for Signifix {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.fmt(f, ".", " ")
}
}
try_from! { i8, i16, i32, i64, i128, isize }
try_from! { u8, u16, u32, u64, u128, usize }
try_from! { f32 }
impl TryFrom<f64> for Signifix {
type Error = Error;
fn try_from(number: f64) -> Result<Self> {
let (numerator, prefix) = {
let number = number.abs();
let prefix = match FACTORS[1..].binary_search_by(|factor|
factor.partial_cmp(&number).unwrap_or(Ordering::Less)
) { Ok(prefix) => prefix, Err(prefix) => prefix };
(number / FACTORS[prefix], prefix)
};
let scaled = |pow: f64| (numerator * pow).round();
let signed = |abs: f64| if number.is_sign_negative()
{ -abs } else { abs };
let middle = scaled(1E+02);
if middle < 1E+04 {
let lower = scaled(1E+03);
if lower < 1E+04 {
if lower < 1E+03 {
Err(Error::OutOfLowerBound(number))
} else {
Ok(Self {
number: super::Signifix {
numerator: signed(lower) as i16,
exponent: 3,
prefix: prefix as u8,
}
})
}
} else {
Ok(Self {
number: super::Signifix {
numerator: signed(middle) as i16,
exponent: 2,
prefix: prefix as u8,
}
})
}
} else {
let upper = scaled(1E+01);
if upper < 1E+04 {
Ok(Self {
number: super::Signifix {
numerator: signed(upper) as i16,
exponent: 1,
prefix: prefix as u8,
}
})
} else {
let above = numerator.round();
if above < 1.024E+03 {
Ok(Self {
number: super::Signifix {
numerator: signed(above) as i16,
exponent: 0,
prefix: prefix as u8,
}
})
} else {
let prefix = prefix + 1;
if prefix < FACTORS.len() {
Ok(Self {
number: super::Signifix {
numerator: signed(1E+03) as i16,
exponent: 3,
prefix: prefix as u8,
}
})
} else {
if number.is_nan() {
Err(Error::Nan)
} else {
Err(Error::OutOfUpperBound(number))
}
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64;
use std::mem::size_of;
fn fmt(number: f64) -> Result<String> {
Signifix::try_from(number).map(|number| format!("{}", number))
}
fn pos(number: f64) -> Result<String> {
Signifix::try_from(number).map(|number| format!("{:+}", number))
}
fn pad(number: f64) -> Result<String> {
Signifix::try_from(number)
.map(|number| format!("{:>1$}", number, DEF_MAX_LEN))
}
#[test]
fn factors_to_symbols() {
assert_eq!(fmt(1_024f64.powi(0)), Ok("1.000 ".into()));
assert_eq!(fmt(1_024f64.powi(1)), Ok("1.000 Ki".into()));
assert_eq!(fmt(1_024f64.powi(2)), Ok("1.000 Mi".into()));
assert_eq!(fmt(1_024f64.powi(3)), Ok("1.000 Gi".into()));
assert_eq!(fmt(1_024f64.powi(4)), Ok("1.000 Ti".into()));
assert_eq!(fmt(1_024f64.powi(5)), Ok("1.000 Pi".into()));
assert_eq!(fmt(1_024f64.powi(6)), Ok("1.000 Ei".into()));
assert_eq!(fmt(1_024f64.powi(7)), Ok("1.000 Zi".into()));
assert_eq!(fmt(1_024f64.powi(8)), Ok("1.000 Yi".into()));
}
#[test]
fn fixed_significance() {
assert_eq!(fmt(1_024f64.powi(0) * 100.0f64), Ok("100.0 ".into()));
assert_eq!(fmt(1_024f64.powi(0) * 123.4f64), Ok("123.4 ".into()));
assert_eq!(fmt(1_024f64.powi(0) * 1_000f64), Ok("1 000 ".into()));
assert_eq!(fmt(1_024f64.powi(0) * 1_002f64), Ok("1 002 ".into()));
assert_eq!(fmt(1_024f64.powi(0) * 1_023f64), Ok("1 023 ".into()));
assert_eq!(fmt(1_024f64.powi(1) * 1.000f64), Ok("1.000 Ki".into()));
assert_eq!(fmt(1_024f64.powi(1) * 1.234f64), Ok("1.234 Ki".into()));
assert_eq!(fmt(1_024f64.powi(1) * 10.00f64), Ok("10.00 Ki".into()));
assert_eq!(fmt(1_024f64.powi(1) * 12.34f64), Ok("12.34 Ki".into()));
assert_eq!(fmt(1_024f64.powi(1) * 100.0f64), Ok("100.0 Ki".into()));
assert_eq!(fmt(1_024f64.powi(1) * 123.4f64), Ok("123.4 Ki".into()));
assert_eq!(fmt(1_024f64.powi(1) * 1_000f64), Ok("1 000 Ki".into()));
assert_eq!(fmt(1_024f64.powi(1) * 1_002f64), Ok("1 002 Ki".into()));
assert_eq!(fmt(1_024f64.powi(1) * 1_023f64), Ok("1 023 Ki".into()));
assert_eq!(fmt(1_024f64.powi(2) * 1.000f64), Ok("1.000 Mi".into()));
assert_eq!(fmt(1_024f64.powi(2) * 1.234f64), Ok("1.234 Mi".into()));
}
#[test]
fn formatting_options() {
assert_eq!(fmt(-1E+00), Ok("-1.000 ".into()));
assert_eq!(fmt( 1E+00), Ok( "1.000 ".into()));
assert_eq!(fmt(-1E+03), Ok("-1 000 ".into()));
assert_eq!(fmt( 1E+03), Ok( "1 000 ".into()));
assert_eq!(pos(-1E+00), Ok("-1.000 ".into()));
assert_eq!(pos( 1E+00), Ok("+1.000 ".into()));
assert_eq!(pos(-1E+03), Ok("-1 000 ".into()));
assert_eq!(pos( 1E+03), Ok("+1 000 ".into()));
assert_eq!(pad(-1E+00), Ok("-1.000 ".into()));
assert_eq!(pad( 1E+00), Ok(" 1.000 ".into()));
assert_eq!(pad(-1E+03), Ok("-1 000 ".into()));
assert_eq!(pad( 1E+03), Ok(" 1 000 ".into()));
}
#[test]
fn lower_prefix_bound() {
assert_eq!(fmt(-0.999_50E+00),
Ok("-1.000 ".into()));
assert_eq!(fmt(-0.999_49E+00),
Err(Error::OutOfLowerBound(-0.999_49E+00)));
}
#[test]
fn upper_prefix_bound() {
assert_eq!(fmt(-1_237.3E+24),
Ok("-1 023 Yi".into()));
assert_eq!(fmt(-1_237.4E+24),
Err(Error::OutOfUpperBound(-1_237.4E+24)));
}
#[test]
fn upper_prefix_round() {
assert_eq!(fmt(1_023.499_999_999_999_94E+00), Ok("1 023 ".into()));
assert_eq!(fmt(1_023.499_999_999_999_95E+00), Ok("1.000 Ki".into()));
}
#[test]
fn fp_category_safety() {
assert_eq!(fmt(0f64),
Err(Error::OutOfLowerBound(0f64)));
assert_eq!(fmt(f64::NEG_INFINITY),
Err(Error::OutOfUpperBound(f64::NEG_INFINITY)));
assert_eq!(fmt(f64::INFINITY),
Err(Error::OutOfUpperBound(f64::INFINITY)));
assert_eq!(fmt(f64::NAN),
Err(Error::Nan));
}
#[test]
fn ord_implementation() {
assert!(Signifix::try_from(1_024f64.powi(1)).unwrap()
< Signifix::try_from(1_024f64.powi(2)).unwrap());
assert!(Signifix::try_from(1E+01).unwrap()
< Signifix::try_from(1E+02).unwrap());
assert!(Signifix::try_from(1E+02).unwrap()
< Signifix::try_from(1E+03).unwrap());
assert!(Signifix::try_from(1E+02).unwrap()
< Signifix::try_from(2E+02).unwrap());
}
#[test]
fn mem_size_of_struct() {
assert_eq!(size_of::<Signifix>(), 4);
}
}