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 y (= ±1E-24) for number {:.3E}", _0)]
OutOfLowerBound(f64),
#[error(display =
"Out of upper bound ±999.9 Y (≈ ±1E+27) 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 = 7;
pub const DEF_MAX_LEN: usize = 8;
pub const ALT_MIN_LEN: usize = 5;
pub const ALT_MAX_LEN: usize = 6;
pub const SYMBOLS: [Option<&str>; 17] = [
Some("y"), Some("z"), Some("a"), Some("f"),
Some("p"), Some("n"), Some("µ"), Some("m"),
None,
Some("k"), Some("M"), Some("G"), Some("T"),
Some("P"), Some("E"), Some("Z"), Some("Y"),
];
pub const FACTORS: [f64; 17] = [
1E-24, 1E-21, 1E-18, 1E-15,
1E-12, 1E-09, 1E-06, 1E-03,
1E+00,
1E+03, 1E+06, 1E+09, 1E+12,
1E+15, 1E+18, 1E+21, 1E+24,
];
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)
-> fmt::Result {
debug_assert_eq!(decimal_mark.chars().count(), 1);
let sign = if self.numerator().is_negative() { "-" } else
if f.sign_plus() { "+" } else { "" };
let (integer, fractional) = self.parts();
if f.alternate() {
let symbol = self.symbol().unwrap_or("#");
f.pad(&format!("{}{}{}{:04$}",
sign, integer.abs(), symbol, fractional,
self.exponent()))
} else {
let symbol = self.symbol().unwrap_or(" ");
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[FACTORS.len() - 1 - 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 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, String)> {
Signifix::try_from(number).map(|number| (
format!("{}", number),
format!("{:#}", number),
))
}
fn pos(number: f64) -> Result<(String, String)> {
Signifix::try_from(number).map(|number| (
format!("{:+}", number),
format!("{:+#}", number),
))
}
fn pad(number: f64) -> Result<(String, String)> {
Signifix::try_from(number).map(|number| (
format!("{:>1$}", number, DEF_MAX_LEN),
format!("{:>#1$}", number, ALT_MAX_LEN),
))
}
#[test]
fn factors_to_symbols() {
assert_eq!(fmt(1E-24), Ok(("1.000 y".into(), "1y000".into())));
assert_eq!(fmt(1E-21), Ok(("1.000 z".into(), "1z000".into())));
assert_eq!(fmt(1E-18), Ok(("1.000 a".into(), "1a000".into())));
assert_eq!(fmt(1E-15), Ok(("1.000 f".into(), "1f000".into())));
assert_eq!(fmt(1E-12), Ok(("1.000 p".into(), "1p000".into())));
assert_eq!(fmt(1E-09), Ok(("1.000 n".into(), "1n000".into())));
assert_eq!(fmt(1E-06), Ok(("1.000 µ".into(), "1µ000".into())));
assert_eq!(fmt(1E-03), Ok(("1.000 m".into(), "1m000".into())));
assert_eq!(fmt(1E+00), Ok(("1.000 ".into(), "1#000".into())));
assert_eq!(fmt(1E+03), Ok(("1.000 k".into(), "1k000".into())));
assert_eq!(fmt(1E+06), Ok(("1.000 M".into(), "1M000".into())));
assert_eq!(fmt(1E+09), Ok(("1.000 G".into(), "1G000".into())));
assert_eq!(fmt(1E+12), Ok(("1.000 T".into(), "1T000".into())));
assert_eq!(fmt(1E+15), Ok(("1.000 P".into(), "1P000".into())));
assert_eq!(fmt(1E+18), Ok(("1.000 E".into(), "1E000".into())));
assert_eq!(fmt(1E+21), Ok(("1.000 Z".into(), "1Z000".into())));
assert_eq!(fmt(1E+24), Ok(("1.000 Y".into(), "1Y000".into())));
}
#[test]
fn fixed_significance() {
assert_eq!(fmt(1.000E+02), Ok(("100.0 ".into(), "100#0".into())));
assert_eq!(fmt(1.234E+02), Ok(("123.4 ".into(), "123#4".into())));
assert_eq!(fmt(1.000E+03), Ok(("1.000 k".into(), "1k000".into())));
assert_eq!(fmt(1.234E+03), Ok(("1.234 k".into(), "1k234".into())));
assert_eq!(fmt(1.000E+04), Ok(("10.00 k".into(), "10k00".into())));
assert_eq!(fmt(1.234E+04), Ok(("12.34 k".into(), "12k34".into())));
assert_eq!(fmt(1.000E+05), Ok(("100.0 k".into(), "100k0".into())));
assert_eq!(fmt(1.234E+05), Ok(("123.4 k".into(), "123k4".into())));
assert_eq!(fmt(1.000E+06), Ok(("1.000 M".into(), "1M000".into())));
assert_eq!(fmt(1.234E+06), Ok(("1.234 M".into(), "1M234".into())));
}
#[test]
fn formatting_options() {
assert_eq!(fmt(-1E+00), Ok(("-1.000 ".into(), "-1#000".into())));
assert_eq!(fmt( 1E+00), Ok(( "1.000 ".into(), "1#000".into())));
assert_eq!(pos(-1E+00), Ok(("-1.000 ".into(), "-1#000".into())));
assert_eq!(pos( 1E+00), Ok(("+1.000 ".into(), "+1#000".into())));
assert_eq!(pad(-1E+00), Ok(("-1.000 ".into(), "-1#000".into())));
assert_eq!(pad( 1E+00), Ok((" 1.000 ".into(), " 1#000".into())));
}
#[test]
fn lower_prefix_bound() {
assert_eq!(fmt(-0.999_50E-24),
Ok(("-1.000 y".into(), "-1y000".into())));
assert_eq!(fmt(-0.999_49E-24),
Err(Error::OutOfLowerBound(-0.999_49E-24)));
}
#[test]
fn upper_prefix_bound() {
assert_eq!(fmt(-999.949E+24),
Ok(("-999.9 Y".into(), "-999Y9".into())));
assert_eq!(fmt(-999.950E+24),
Err(Error::OutOfUpperBound(-999.950E+24)));
}
#[test]
fn upper_prefix_round() {
assert_eq!(fmt(999.949_999_999_999_8E+00),
Ok(("999.9 ".into(), "999#9".into())));
assert_eq!(fmt(999.949_999_999_999_9E+00),
Ok(("1.000 k".into(), "1k000".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(1E+03).unwrap()
< Signifix::try_from(1E+06).unwrap());
assert!(Signifix::try_from(1E+01).unwrap()
< Signifix::try_from(1E+02).unwrap());
assert!(Signifix::try_from(1E+03).unwrap()
< Signifix::try_from(2E+03).unwrap());
}
#[test]
fn mem_size_of_struct() {
assert_eq!(size_of::<Signifix>(), 4);
}
}