#![warn(missing_docs)]
use std::{cmp::*, error, fmt, hash::*};
use Precision::*;
mod parse;
pub use parse::ParseError;
pub type Result = std::result::Result<Formatter, Error>;
const SN_BIG_CUTOFF: f64 = 1_000_000_000_000f64;
const SN_SML_CUTOFF: f64 = 0.001;
const SN_PREC: Precision = Significance(7);
const PREFIX_LIM: usize = 12;
const UNITS_LIM: usize = 12;
const SUFFIX_LIM: usize = 12;
const FLOATBUF_LEN: usize = 22;
const BUF_LEN: usize = PREFIX_LIM + FLOATBUF_LEN + 3 + UNITS_LIM + SUFFIX_LIM;
#[derive(Debug, Clone)]
pub struct Formatter {
strbuf: Vec<u8>,
thou_sep: Option<u8>,
start: usize,
precision: Precision,
scales: Scales,
suffix: [u8; SUFFIX_LIM],
suffix_len: usize,
convert: fn(f64) -> f64,
}
impl Formatter {
pub fn new() -> Self {
Self {
strbuf: vec![0; BUF_LEN],
thou_sep: None,
start: 0,
precision: Precision::Unspecified,
scales: Scales::none(),
suffix: [0; SUFFIX_LIM],
suffix_len: 0,
convert: |x| x,
}
}
pub fn currency(prefix: &str) -> Result {
Self::new()
.separator(',')
.unwrap()
.precision(Decimals(2))
.prefix(prefix)
}
pub fn percentage() -> Self {
Self::new().convert(|x| x * 100.0).suffix("%").unwrap()
}
pub fn convert(mut self, f: fn(f64) -> f64) -> Self {
self.convert = f;
self
}
pub fn precision(mut self, precision: Precision) -> Self {
self.precision = precision;
self
}
pub fn scales(mut self, scales: Scales) -> Self {
self.scales = scales;
self
}
pub fn build_scales(mut self, base: u16, units: Vec<&'static str>) -> Result {
let scales = Scales::new(base, units)?;
self.scales = scales;
Ok(self)
}
pub fn separator<S: Into<Option<char>>>(mut self, sep: S) -> Result {
if let Some(sep) = sep.into() {
if sep.len_utf8() != 1 {
Err(Error::InvalidSeparator(sep))
} else {
let mut buf = [0];
sep.encode_utf8(&mut buf);
self.thou_sep = Some(buf[0]);
Ok(self)
}
} else {
self.thou_sep = None;
Ok(self)
}
}
pub fn prefix(mut self, prefix: &str) -> Result {
if prefix.len() > PREFIX_LIM {
Err(Error::InvalidPrefix(prefix.to_string()))
} else {
let n = prefix.len();
self.strbuf[..n].copy_from_slice(prefix.as_bytes());
self.start = n;
Ok(self)
}
}
pub fn suffix(mut self, suffix: &str) -> Result {
if suffix.len() > SUFFIX_LIM {
Err(Error::InvalidSuffix(suffix.to_string()))
} else {
let n = suffix.len();
self.suffix[..n].copy_from_slice(suffix.as_bytes());
self.suffix_len = n;
Ok(self)
}
}
pub fn fmt(&mut self, num: f64) -> &str {
if num.is_nan() {
"NaN"
} else if num.is_infinite() && num.is_sign_negative() {
"-∞"
} else if num.is_infinite() {
"∞"
} else if num == 0.0 {
"0"
} else {
let num = (self.convert)(num);
let (scaled, unit) = self.scales.scale(num);
let abs = scaled.abs();
let sn_sml_cutoff = match self.precision {
Decimals(d) | Significance(d) if d <= 3 => 10f64.powi(d as i32).recip(),
_ => SN_SML_CUTOFF,
};
if abs >= SN_BIG_CUTOFF || abs < sn_sml_cutoff {
let (num, exponent) = reduce_to_sn(num);
let precision = match self.precision {
Unspecified => SN_PREC,
x => x,
};
let cursor = self.start + self.write_num(num, precision);
self.strbuf[cursor] = b'e'; let cursor = 1 + cursor;
let written = {
let mut buf = itoa::Buffer::new();
let s = buf.format(exponent);
let end = cursor + s.len();
self.strbuf[cursor..end].copy_from_slice(s.as_bytes());
s.len()
};
let cursor = cursor + written;
self.apply_suffix_and_output(cursor)
} else {
let mut cursor = self.start + self.write_num(scaled, self.precision);
if !unit.is_empty() {
let s = cursor;
cursor += unit.len();
self.strbuf[s..cursor].copy_from_slice(unit.as_bytes());
}
self.apply_suffix_and_output(cursor)
}
}
}
fn write_num(&mut self, num: f64, precision: Precision) -> usize {
let mut tmp = dtoa::Buffer::new();
let s = tmp.format(num);
let tmp = s.as_bytes();
let n = tmp.len();
let mut digits = 0;
let mut written = 0;
let mut in_frac = false;
let mut thou = 2 - (num.abs().log10().trunc() as u8) % 3;
let mut idx = self.start;
for i in 0..n {
let byte = tmp[i]; self.strbuf[idx] = byte; idx += 1;
written += 1;
if byte.is_ascii_digit() {
digits += 1;
thou += 1;
}
if i + 1 < n && tmp[i + 1] == b'.' {
in_frac = true;
if let Decimals(_) = precision {
digits = 0
}
}
if !in_frac && thou == 3 {
if let Some(sep) = self.thou_sep {
thou = 0;
self.strbuf[idx] = sep;
idx += 1;
written += 1;
}
}
match precision {
Significance(d) | Decimals(d) if in_frac => {
if digits >= d {
break;
}
}
_ => (),
}
}
written
}
fn apply_suffix_and_output(&mut self, mut pos: usize) -> &str {
if !self.suffix.is_empty() {
let s = pos;
pos = s + self.suffix_len;
self.strbuf[s..pos].copy_from_slice(&self.suffix[..self.suffix_len]);
}
std::str::from_utf8(&self.strbuf[..pos]).expect("will be valid string")
}
}
impl Default for Formatter {
fn default() -> Self {
Self::new()
.separator(',')
.unwrap()
.scales(Scales::short())
.precision(Decimals(3))
}
}
impl std::str::FromStr for Formatter {
type Err = parse::ParseError;
fn from_str(s: &str) -> std::result::Result<Self, ParseError> {
parse::parse_formatter(s)
}
}
impl PartialEq for Formatter {
#[allow(clippy::suspicious_operation_groupings)]
fn eq(&self, other: &Self) -> bool {
self.convert == other.convert
&& self.precision == other.precision
&& self.thou_sep == other.thou_sep
&& self.suffix[..self.suffix_len] == other.suffix[..other.suffix_len]
&& self.strbuf[..self.start] == other.strbuf[..other.start]
&& self.scales == other.scales
}
}
impl Eq for Formatter {}
impl Hash for Formatter {
fn hash<H: Hasher>(&self, hasher: &mut H) {
self.strbuf[..self.start].hash(hasher);
self.thou_sep.hash(hasher);
self.precision.hash(hasher);
self.scales.hash(hasher);
self.suffix[..self.suffix_len].hash(hasher);
self.convert.hash(hasher);
}
}
fn reduce_to_sn(n: f64) -> (f64, i32) {
if n == 0.0 || n == -0.0 {
(0.0, 0)
} else {
let abs = n.abs();
let mut e = abs.log10().trunc() as i32;
if abs < 1.0 {
e -= 1;
}
let n = n * 10_f64.powi(-e);
(n, e)
}
}
#[derive(Debug, PartialEq)]
pub enum Error {
InvalidPrefix(String),
InvalidSeparator(char),
InvalidSuffix(String),
InvalidUnit(&'static str),
ZeroBase,
}
impl error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Error::*;
match self {
InvalidPrefix(prefix) => write!(
f,
"Invalid prefix `{}`. Prefix is longer than the supported {} bytes",
prefix, PREFIX_LIM
),
InvalidSeparator(sep) => write!(
f,
"Invalid separator `{}`. Separator can only be one byte long",
sep
),
InvalidSuffix(suffix) => write!(
f,
"Invalid suffix `{}`. Suffix is longer than the supported {} bytes",
suffix, SUFFIX_LIM
),
InvalidUnit(unit) => write!(
f,
"Invalid unit `{}`. Unit is longer than the supported {} bytes",
unit, UNITS_LIM
),
ZeroBase => write!(f, "Invalid scale base, base must be greater than zero"),
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
#[allow(missing_docs)]
pub enum Precision {
Significance(u8),
Decimals(u8),
Unspecified,
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct Scales {
base: u16,
units: Vec<&'static str>,
}
impl Scales {
pub fn new(base: u16, units: Vec<&'static str>) -> std::result::Result<Self, Error> {
if base == 0 {
return Err(Error::ZeroBase);
}
for unit in &units {
if unit.len() > UNITS_LIM {
return Err(Error::InvalidUnit(unit));
}
}
Ok(Self { base, units })
}
pub fn none() -> Self {
Self {
base: std::u16::MAX,
units: Vec::new(),
}
}
pub fn short() -> Self {
Scales {
base: 1000,
units: vec!["", " K", " M", " B", " T", " P", " E", " Z", " Y"],
}
}
pub fn metric() -> Self {
Scales {
base: 1000,
units: vec![" ", " k", " M", " G", " T", " P", " E", " Z", " Y"],
}
}
pub fn binary() -> Self {
Scales {
base: 1024,
units: vec![" ", " ki", " Mi", " Gi", " Ti", " Pi", " Ei", " Zi", " Yi"],
}
}
pub fn base(&self) -> u16 {
self.base
}
pub fn units(&self) -> &[&'static str] {
self.units.as_slice()
}
pub fn into_inner(self) -> (u16, Vec<&'static str>) {
(self.base, self.units)
}
pub fn scale(&self, mut num: f64) -> (f64, &'static str) {
let base = self.base as f64;
let mut u = "";
let mut n2 = num;
for unit in &self.units {
num = n2;
u = unit;
if num.abs() >= base {
n2 = num / base;
} else {
break;
}
}
(num, u)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::*;
#[test]
fn nan_and_inf() {
let mut f = Formatter::new();
assert_eq!(f.fmt(INFINITY), "∞");
assert_eq!(f.fmt(NEG_INFINITY), "-∞");
assert_eq!(f.fmt(NAN), "NaN");
}
#[test]
fn invalid_sep() {
let f = Formatter::new().separator('ß');
assert_eq!(f, Err(Error::InvalidSeparator('ß')));
}
#[test]
fn no_sep() {
let mut f = Formatter::default();
assert_eq!(f.thou_sep, Some(b','));
f = f.separator(None).unwrap();
assert_eq!(f.thou_sep, None);
}
#[test]
fn sn_reduction() {
let f = reduce_to_sn;
assert_eq!(f(0.0), (0.0, 0));
assert_eq!(f(1.23), (1.23, 0));
assert_eq!(f(12.34), (1.234, 1));
assert_eq!(f(1234.567), (1.234567, 3));
assert_eq!(f(1234.567e13), (1.234567, 16));
assert_eq!(f(0.0123), (1.23, -2));
assert_eq!(f(0.123), (1.23, -1));
assert_eq!(f(0.0012345), (1.2345, -3));
assert_eq!(f(0.00123e-12), (1.23, -15));
assert_eq!(f(0.00123e12), (1.23, 9));
assert_eq!(f(1234e-12), (1.234, -9));
assert_eq!(f(-0.0), (-0.0, 0));
assert_eq!(f(-1.23), (-1.23, 0));
assert_eq!(f(-12.34), (-1.234, 1));
assert_eq!(f(-1234.567), (-1.234567, 3));
assert_eq!(f(-1234.567e13), (-1.234567, 16));
assert_eq!(f(-0.0123), (-1.23, -2));
assert_eq!(f(-0.123), (-1.23, -1));
assert_eq!(f(-0.0012345), (-1.2345, -3));
assert_eq!(f(-0.00123e-12), (-1.23, -15));
assert_eq!(f(-0.00123e12), (-1.23, 9));
assert_eq!(f(-1234e-12), (-1.234, -9));
}
#[test]
fn sn_tests() {
let mut f = Formatter::new().scales(Scales::none());
assert_eq!(f.fmt(123.4567e43), "1.234567e45");
assert_eq!(f.fmt(123.4567e-43), "1.234567e-41");
assert_eq!(f.fmt(-123.4567e-43), "-1.234567e-41");
assert_eq!(f.fmt(-123.4567e43), "-1.234567e45");
assert_eq!(f.fmt(0.000000007894), "7.893999e-9");
assert_eq!(f.fmt(123454023590854.0), "1.234540e14");
assert_eq!(f.fmt(123.456789e99), "1.234567e101");
}
#[test]
fn separator_tests() {
let mut f = Formatter::new()
.separator(',')
.unwrap()
.scales(Scales::none());
assert_eq!(f.fmt(123456789_f64), "123,456,789.0");
assert_eq!(f.fmt(12345678_f64), "12,345,678.0");
assert_eq!(f.fmt(1234567_f64), "1,234,567.0");
assert_eq!(f.fmt(123456_f64), "123,456.0");
assert_eq!(f.fmt(1234_f64), "1,234.0");
assert_eq!(f.fmt(123_f64), "123.0");
assert_eq!(f.fmt(0.0), "0");
assert_eq!(f.fmt(0.1234), "0.1234");
assert_eq!(f.fmt(-123.0), "-123.0");
assert_eq!(f.fmt(-1234.0), "-1,234.0");
assert_eq!(f.fmt(-1234567.0), "-1,234,567.0");
assert_eq!(f.fmt(-123456789101.0), "-123,456,789,101.0");
}
#[test]
fn test_scaling() {
let s = Scales::short();
assert_eq!(s.scale(123.0), (123.0, ""));
assert_eq!(s.scale(-123.0), (-123.0, ""));
assert_eq!(s.scale(1234.0), (1.234, " K"));
assert_eq!(s.scale(-1234.0), (-1.234, " K"));
assert_eq!(s.scale(-123456.0), (-123.456, " K"));
assert_eq!(s.scale(-12345678.0), (-12.345678, " M"));
let s = Scales::binary();
assert_eq!(s.scale(123.0), (123.0, " "));
assert_eq!(s.scale(1024.0 * 1024.0), (1.0, " Mi"));
let s = Scales::new(2, vec!["x1", "x2", "x4", "x8", "x16"]).unwrap();
assert_eq!(s.scale(20.0), (1.25, "x16"));
assert_eq!(s.scale(64.0), (4.0, "x16"));
let s = Scales::none();
assert_eq!(s.scale(-1_000_000f64), (-1_000_000f64, ""));
}
#[test]
fn scaling_inside_fmtr() {
let mut f = Formatter::default().precision(Unspecified);
assert_eq!(f.fmt(12345678.0), "12.345678 M");
assert_eq!(f.fmt(-12345.0), "-12.345 K");
assert_eq!(f.fmt(-123.0), "-123.0");
assert_eq!(f.fmt(-0.00123), "-0.00123");
}
#[test]
fn prefix() {
let mut f = Formatter::new()
.separator(',')
.unwrap()
.prefix("$")
.unwrap();
assert_eq!(f.fmt(123456.0), "$123,456.0");
assert_eq!(f.fmt(0.01234), "$0.01234");
}
#[test]
fn suffix() {
let mut f = Formatter::new()
.separator(',')
.unwrap()
.suffix("%")
.unwrap();
assert_eq!(f.fmt(123456.0), "123,456.0%");
assert_eq!(f.fmt(0.1234), "0.1234%");
}
#[test]
fn buf_lim_testing() {
let mut f = Formatter::new()
.build_scales(1, vec!["_ten chars"])
.unwrap()
.separator(',')
.unwrap()
.prefix("__ chars _")
.unwrap()
.suffix("a suffix !")
.unwrap();
assert_eq!(
f.fmt(-123456789.0123456789),
"__ chars _-123,456,789.01234567_ten charsa suffix !"
);
}
#[test]
fn decimals_test() {
let mut f = Formatter::new().precision(Decimals(6));
assert_eq!(f.fmt(1234.5), "1234.5");
assert_eq!(f.fmt(123.456789111), "123.456789");
f = Formatter::default()
.scales(Scales::none())
.precision(Decimals(0));
assert_eq!(f.fmt(1123.456), "1,123");
assert_eq!(f.fmt(12345678.90123), "12,345,678");
f = Formatter::default().precision(Decimals(1));
assert_eq!(f.fmt(0.001234), "1.2e-3");
f = Formatter::default().precision(Significance(1));
assert_eq!(f.fmt(0.001234), "1e-3");
}
#[test]
fn significance_test() {
let mut f = Formatter::default().precision(Significance(2));
assert_eq!(f.fmt(1234.0), "1.2 K");
assert_eq!(f.fmt(1.02), "1.0");
}
#[test]
fn currency_test() {
let mut f = Formatter::currency("$").unwrap();
assert_eq!(f.fmt(12345.6789), "$12,345.67");
assert_eq!(f.fmt(1234_f64), "$1,234.0");
let f = Formatter::currency("invalid length prefix");
assert_eq!(
f,
Err(Error::InvalidPrefix("invalid length prefix".to_string()))
);
}
#[test]
fn percentage_tests() {
let mut f = Formatter::percentage();
assert_eq!(f.fmt(0.678912), "67.8912%");
assert_eq!(f.fmt(1.23), "123.0%");
assert_eq!(f.fmt(1.2), "120.0%");
}
#[test]
fn failures() {
use Error::*;
let invalid = "invalid length prefix";
let f = Formatter::new().prefix(invalid);
assert_eq!(f, Err(InvalidPrefix(invalid.to_string())));
assert_eq!(
&f.unwrap_err().to_string(),
"Invalid prefix `invalid length prefix`. Prefix is longer than the supported 12 bytes"
);
let f = Formatter::new().suffix(invalid);
assert_eq!(f, Err(InvalidSuffix(invalid.to_string())));
assert_eq!(
&f.unwrap_err().to_string(),
"Invalid suffix `invalid length prefix`. Suffix is longer than the supported 12 bytes"
);
let f = Formatter::new().build_scales(1000, vec![invalid]);
assert_eq!(f, Err(InvalidUnit(invalid)));
assert_eq!(
&f.unwrap_err().to_string(),
"Invalid unit `invalid length prefix`. Unit is longer than the supported 12 bytes"
);
let f = Formatter::new().build_scales(0, vec![""]);
assert_eq!(f, Err(ZeroBase));
assert_eq!(
&f.unwrap_err().to_string(),
"Invalid scale base, base must be greater than zero"
);
let f = Formatter::new().separator('😃');
assert_eq!(f, Err(InvalidSeparator('😃')));
assert_eq!(
&f.unwrap_err().to_string(),
"Invalid separator `😃`. Separator can only be one byte long"
);
}
#[test]
fn getters() {
let s = Scales::new(12, vec!["", "one"]).unwrap();
assert_eq!(s.base(), 12);
assert_eq!(s.units(), &["", "one"]);
let (base, units) = s.into_inner();
assert_eq!(base, 12);
assert_eq!(units, &["", "one"]);
}
#[test]
fn eq_and_hashing() {
let f1 = Formatter::default()
.prefix("Hi")
.unwrap()
.suffix("Bye")
.unwrap();
let f2 = Formatter::new()
.separator(',')
.unwrap()
.prefix("Hi")
.unwrap()
.suffix("Bye")
.unwrap()
.scales(Scales::short())
.precision(Decimals(3));
let f3 = Formatter::new();
assert_eq!(f1, f2);
assert_ne!(f1, f3);
assert_ne!(f2, f3);
let mut h = std::collections::hash_map::DefaultHasher::new();
f1.hash(&mut h);
let h1 = h.finish();
let mut h = std::collections::hash_map::DefaultHasher::new();
f2.hash(&mut h);
let h2 = h.finish();
let mut h = std::collections::hash_map::DefaultHasher::new();
f3.hash(&mut h);
let h3 = h.finish();
assert_eq!(h1, h2);
assert_ne!(h1, h3);
assert_ne!(h2, h3);
}
#[test]
fn panicking_number() {
let mut fmtr = Formatter::default()
.precision(Precision::Unspecified)
.scales(Scales::none());
let s = fmtr.fmt(0.00316114);
assert_eq!(s, "0.0031611399999999999");
let s = fmtr.fmt(2_f64.powi(67));
assert_eq!(s, "1.475739e20");
}
#[test]
fn panicking_number2() {
let mut f = Formatter::default();
let s = f.fmt(-0.0025053862329988824);
assert_eq!(s, "-0.002");
}
}