parent
8b539ab7ac
commit
e0bbe29dc2
@ -0,0 +1,452 @@ |
||||
use std::convert::{TryFrom, TryInto}; |
||||
use std::error::Error; |
||||
use std::fmt; |
||||
use std::fmt::Write; |
||||
use std::ops::Neg; |
||||
use std::str::FromStr; |
||||
|
||||
const DECIMAL_PART_DIGITS: usize = 18; |
||||
const DECIMAL_PART_POW: i128 = 1_000_000_000_000_000_000; |
||||
const DECIMAL_PART_POW_MINUS_ONE: i128 = 100_000_000_000_000_000; |
||||
const DECIMAL_PART_HALF_POW: i128 = 1_000_000_000; |
||||
|
||||
/// [XML Schema `decimal` datatype](https://www.w3.org/TR/xmlschema11-2/#decimal) implementation.
|
||||
///
|
||||
/// It stores the decimal in a fix point encoding allowing nearly 18 digits before and 18 digits after ".".
|
||||
#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Copy, Hash, Default)] |
||||
pub struct Decimal { |
||||
value: i128, // value * 10^18
|
||||
} |
||||
|
||||
impl Decimal { |
||||
pub fn from_le_bytes(bytes: [u8; 16]) -> Self { |
||||
Self { |
||||
value: i128::from_le_bytes(bytes), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl<I: Into<i64>> From<I> for Decimal { |
||||
fn from(value: I) -> Self { |
||||
let value: i64 = value.into(); |
||||
Self { |
||||
value: i128::from(value) * DECIMAL_PART_POW, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl FromStr for Decimal { |
||||
type Err = ParseDecimalError; |
||||
|
||||
/// Parses decimals lexical mapping
|
||||
fn from_str(input: &str) -> Result<Self, ParseDecimalError> { |
||||
// (\+|-)?([0-9]+(\.[0-9]*)?|\.[0-9]+)
|
||||
let input = input.as_bytes(); |
||||
if input.is_empty() { |
||||
return Err(PARSE_UNEXPECTED_END); |
||||
} |
||||
|
||||
let (sign, mut cursor) = match input.get(0) { |
||||
Some(b'+') => (1, 1), |
||||
Some(b'-') => (-1, 1), |
||||
_ => (1, 0), |
||||
}; |
||||
|
||||
let mut value = 0i128; |
||||
let mut with_before_dot = false; |
||||
while cursor < input.len() && b'0' <= input[cursor] && input[cursor] <= b'9' { |
||||
value = value |
||||
.checked_mul(10) |
||||
.ok_or(PARSE_OVERFLOW)? |
||||
.checked_add((input[cursor] - b'0').into()) |
||||
.ok_or(PARSE_OVERFLOW)?; |
||||
cursor += 1; |
||||
with_before_dot = true; |
||||
} |
||||
|
||||
let mut exp = DECIMAL_PART_POW; |
||||
if input.len() > cursor { |
||||
if input[cursor] != b'.' { |
||||
return Err(PARSE_UNEXPECTED_CHAR); |
||||
} |
||||
cursor += 1; |
||||
|
||||
let mut with_after_dot = false; |
||||
while cursor < input.len() && b'0' <= input[cursor] && input[cursor] <= b'9' { |
||||
exp = exp.checked_div(10).ok_or(PARSE_UNDERFLOW)?; |
||||
value = value |
||||
.checked_mul(10) |
||||
.ok_or(PARSE_OVERFLOW)? |
||||
.checked_add((input[cursor] - b'0').into()) |
||||
.ok_or(PARSE_OVERFLOW)?; |
||||
cursor += 1; |
||||
with_after_dot = true; |
||||
} |
||||
|
||||
if !with_before_dot && !with_after_dot { |
||||
//We only have a dot
|
||||
return Err(PARSE_UNEXPECTED_END); |
||||
} |
||||
if input.len() > cursor { |
||||
return Err(PARSE_UNEXPECTED_CHAR); |
||||
} |
||||
} else if !with_before_dot { |
||||
//It's empty
|
||||
return Err(PARSE_UNEXPECTED_END); |
||||
} |
||||
|
||||
Ok(Self { |
||||
value: value |
||||
.checked_mul(sign) |
||||
.ok_or(PARSE_OVERFLOW)? |
||||
.checked_mul(exp) |
||||
.ok_or(PARSE_OVERFLOW)?, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
#[derive(Debug, Clone)] |
||||
pub struct ParseDecimalError { |
||||
kind: ParseDecimalErrorKind, |
||||
} |
||||
|
||||
#[derive(Debug, Clone)] |
||||
enum ParseDecimalErrorKind { |
||||
Overflow, |
||||
Underflow, |
||||
UnexpectedChar, |
||||
UnexpectedEnd, |
||||
} |
||||
|
||||
const PARSE_OVERFLOW: ParseDecimalError = ParseDecimalError { |
||||
kind: ParseDecimalErrorKind::Overflow, |
||||
}; |
||||
const PARSE_UNDERFLOW: ParseDecimalError = ParseDecimalError { |
||||
kind: ParseDecimalErrorKind::Underflow, |
||||
}; |
||||
const PARSE_UNEXPECTED_CHAR: ParseDecimalError = ParseDecimalError { |
||||
kind: ParseDecimalErrorKind::UnexpectedChar, |
||||
}; |
||||
const PARSE_UNEXPECTED_END: ParseDecimalError = ParseDecimalError { |
||||
kind: ParseDecimalErrorKind::UnexpectedEnd, |
||||
}; |
||||
|
||||
impl fmt::Display for ParseDecimalError { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
match self.kind { |
||||
ParseDecimalErrorKind::Overflow => write!(f, "Value overflow"), |
||||
ParseDecimalErrorKind::Underflow => write!(f, "Value underflow"), |
||||
ParseDecimalErrorKind::UnexpectedChar => write!(f, "Unexpected character"), |
||||
ParseDecimalErrorKind::UnexpectedEnd => write!(f, "Unexpected end of string"), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl Error for ParseDecimalError {} |
||||
|
||||
impl fmt::Display for Decimal { |
||||
/// Formats the decimal following its canonical representation
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
let mut value = self.value; |
||||
if value < 0 { |
||||
f.write_char('-')?; |
||||
} |
||||
|
||||
let mut digits = [b'0'; 40]; |
||||
let mut i = 0; |
||||
while value != 0 { |
||||
digits[i] = b'0' + ((value % 10).abs() as u8); |
||||
value /= 10; |
||||
i += 1; |
||||
} |
||||
|
||||
if i == 0 { |
||||
return f.write_char('0'); |
||||
} |
||||
|
||||
let last_non_zero = i - 1; |
||||
let first_non_zero = digits |
||||
.iter() |
||||
.cloned() |
||||
.enumerate() |
||||
.find(|(_, v)| *v != b'0') |
||||
.map(|(i, _)| i) |
||||
.unwrap_or(40); |
||||
|
||||
if last_non_zero >= DECIMAL_PART_DIGITS { |
||||
for c in digits[DECIMAL_PART_DIGITS..=last_non_zero].iter().rev() { |
||||
f.write_char(char::from(*c))?; |
||||
} |
||||
} else { |
||||
f.write_char('0')? |
||||
} |
||||
if DECIMAL_PART_DIGITS > first_non_zero { |
||||
f.write_char('.')?; |
||||
for c in digits[first_non_zero..DECIMAL_PART_DIGITS].iter().rev() { |
||||
f.write_char(char::from(*c))?; |
||||
} |
||||
} |
||||
|
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
impl Neg for Decimal { |
||||
type Output = Self; |
||||
|
||||
fn neg(self) -> Self { |
||||
Self { |
||||
value: self.value.neg(), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl Decimal { |
||||
/*pub fn trunc(self) -> i64 {
|
||||
(self.value / DECIMAL_PART_POW) as i64 |
||||
}*/ |
||||
|
||||
pub fn to_le_bytes(&self) -> [u8; 16] { |
||||
self.value.to_le_bytes() |
||||
} |
||||
|
||||
/// [op:numeric-add](https://www.w3.org/TR/xpath-functions/#func-numeric-add)
|
||||
pub fn checked_add(&self, rhs: Self) -> Option<Self> { |
||||
Some(Self { |
||||
value: self.value.checked_add(rhs.value)?, |
||||
}) |
||||
} |
||||
|
||||
/// [op:numeric-subtract](https://www.w3.org/TR/xpath-functions/#func-numeric-subtract)
|
||||
pub fn checked_sub(&self, rhs: Self) -> Option<Self> { |
||||
Some(Self { |
||||
value: self.value.checked_sub(rhs.value)?, |
||||
}) |
||||
} |
||||
|
||||
/// [op:numeric-multiply](https://www.w3.org/TR/xpath-functions/#func-numeric-multiply)
|
||||
pub fn checked_mul(&self, rhs: Self) -> Option<Self> { |
||||
//TODO: better algorithm to keep precision
|
||||
Some(Self { |
||||
value: self |
||||
.value |
||||
.checked_div(DECIMAL_PART_HALF_POW)? |
||||
.checked_mul(rhs.value.checked_div(DECIMAL_PART_HALF_POW)?)?, |
||||
}) |
||||
} |
||||
|
||||
/// [op:numeric-divide](https://www.w3.org/TR/xpath-functions/#func-numeric-divide)
|
||||
pub fn checked_div(&self, rhs: Self) -> Option<Self> { |
||||
//TODO: better algorithm to keep precision
|
||||
Some(Self { |
||||
value: self |
||||
.value |
||||
.checked_mul(DECIMAL_PART_HALF_POW)? |
||||
.checked_div(rhs.value)? |
||||
.checked_mul(DECIMAL_PART_HALF_POW)?, |
||||
}) |
||||
} |
||||
|
||||
/// [fn:abs](https://www.w3.org/TR/xpath-functions/#func-abs)
|
||||
pub fn abs(&self) -> Decimal { |
||||
Self { |
||||
value: self.value.abs(), |
||||
} |
||||
} |
||||
|
||||
/// [fn:round](https://www.w3.org/TR/xpath-functions/#func-round)
|
||||
pub fn round(&self) -> Decimal { |
||||
let value = self.value / DECIMAL_PART_POW_MINUS_ONE; |
||||
Self { |
||||
value: if value >= 0 { |
||||
(value / 10 + if value % 10 >= 5 { 1 } else { 0 }) * DECIMAL_PART_POW |
||||
} else { |
||||
(value / 10 + if -value % 10 > 5 { -1 } else { 0 }) * DECIMAL_PART_POW |
||||
}, |
||||
} |
||||
} |
||||
|
||||
/// [fn:ceiling](https://www.w3.org/TR/xpath-functions/#func-ceiling)
|
||||
pub fn ceil(&self) -> Decimal { |
||||
Self { |
||||
value: if self.value >= 0 && self.value % DECIMAL_PART_POW != 0 { |
||||
(self.value / DECIMAL_PART_POW + 1) * DECIMAL_PART_POW |
||||
} else { |
||||
(self.value / DECIMAL_PART_POW) * DECIMAL_PART_POW |
||||
}, |
||||
} |
||||
} |
||||
|
||||
/// [fn:floor](https://www.w3.org/TR/xpath-functions/#func-floor)
|
||||
pub fn floor(&self) -> Decimal { |
||||
Self { |
||||
value: if self.value >= 0 || self.value % DECIMAL_PART_POW == 0 { |
||||
(self.value / DECIMAL_PART_POW) * DECIMAL_PART_POW |
||||
} else { |
||||
(self.value / DECIMAL_PART_POW - 1) * DECIMAL_PART_POW |
||||
}, |
||||
} |
||||
} |
||||
|
||||
pub fn to_f32(&self) -> Option<f32> { |
||||
//TODO: precision?
|
||||
Some((self.value as f32) / (DECIMAL_PART_POW as f32)) |
||||
} |
||||
|
||||
pub fn to_f64(&self) -> Option<f64> { |
||||
//TODO: precision?
|
||||
Some((self.value as f64) / (DECIMAL_PART_POW as f64)) |
||||
} |
||||
} |
||||
|
||||
impl TryFrom<Decimal> for i64 { |
||||
type Error = (); |
||||
|
||||
fn try_from(value: Decimal) -> Result<i64, ()> { |
||||
value |
||||
.value |
||||
.checked_div(DECIMAL_PART_POW) |
||||
.ok_or(())? |
||||
.try_into() |
||||
.map_err(|_| ()) |
||||
} |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod tests { |
||||
use super::*; |
||||
use std::i128; |
||||
use std::i64; |
||||
|
||||
const MIN: Decimal = Decimal { value: i128::MIN }; |
||||
const MAX: Decimal = Decimal { value: i128::MAX }; |
||||
const STEP: Decimal = Decimal { value: 1 }; |
||||
|
||||
#[test] |
||||
fn from_str() { |
||||
assert_eq!(Decimal::from_str("210").unwrap().to_string(), "210"); |
||||
assert_eq!(Decimal::from_str("1000").unwrap().to_string(), "1000"); |
||||
assert_eq!(Decimal::from_str("-1.23").unwrap().to_string(), "-1.23"); |
||||
assert_eq!( |
||||
Decimal::from_str("12678967.543233").unwrap().to_string(), |
||||
"12678967.543233" |
||||
); |
||||
assert_eq!( |
||||
Decimal::from_str("+100000.00").unwrap().to_string(), |
||||
"100000" |
||||
); |
||||
assert_eq!(Decimal::from_str("0.1220").unwrap().to_string(), "0.122"); |
||||
assert_eq!(Decimal::from_str(".12200").unwrap().to_string(), "0.122"); |
||||
assert_eq!(Decimal::from_str(&MAX.to_string()).unwrap(), MAX); |
||||
assert_eq!( |
||||
Decimal::from_str(&MIN.checked_add(STEP).unwrap().to_string()).unwrap(), |
||||
MIN.checked_add(STEP).unwrap() |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn add() { |
||||
assert!(MIN.checked_add(STEP).is_some()); |
||||
assert!(MAX.checked_add(STEP).is_none()); |
||||
assert_eq!(MAX.checked_add(MIN), Some(-STEP)); |
||||
} |
||||
|
||||
#[test] |
||||
fn sub() { |
||||
assert!(MIN.checked_sub(STEP).is_none()); |
||||
assert!(MAX.checked_sub(STEP).is_some()); |
||||
} |
||||
|
||||
#[test] |
||||
fn mul() { |
||||
assert_eq!( |
||||
Decimal::from_str("1") |
||||
.unwrap() |
||||
.checked_mul(Decimal::from_str("-1").unwrap()), |
||||
Some(Decimal::from_str("-1").unwrap()) |
||||
); |
||||
assert_eq!( |
||||
Decimal::from_str("1000") |
||||
.unwrap() |
||||
.checked_mul(Decimal::from_str("1000").unwrap()), |
||||
Some(Decimal::from_str("1000000").unwrap()) |
||||
); |
||||
assert_eq!( |
||||
Decimal::from_str("0.1") |
||||
.unwrap() |
||||
.checked_mul(Decimal::from_str("0.01").unwrap()), |
||||
Some(Decimal::from_str("0.001").unwrap()) |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn div() { |
||||
assert_eq!( |
||||
Decimal::from_str("1") |
||||
.unwrap() |
||||
.checked_div(Decimal::from_str("1").unwrap()), |
||||
Some(Decimal::from_str("1").unwrap()) |
||||
); |
||||
assert_eq!( |
||||
Decimal::from_str("100") |
||||
.unwrap() |
||||
.checked_div(Decimal::from_str("10").unwrap()), |
||||
Some(Decimal::from_str("10").unwrap()) |
||||
); |
||||
assert_eq!( |
||||
Decimal::from_str("10") |
||||
.unwrap() |
||||
.checked_div(Decimal::from_str("100").unwrap()), |
||||
Some(Decimal::from_str("0.1").unwrap()) |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn round() { |
||||
assert_eq!(Decimal::from_str("10").unwrap().round(), Decimal::from(10)); |
||||
assert_eq!( |
||||
Decimal::from_str("-10").unwrap().round(), |
||||
Decimal::from(-10) |
||||
); |
||||
assert_eq!(Decimal::from_str("2.5").unwrap().round(), Decimal::from(3)); |
||||
assert_eq!( |
||||
Decimal::from_str("2.4999").unwrap().round(), |
||||
Decimal::from(2) |
||||
); |
||||
assert_eq!( |
||||
Decimal::from_str("-2.5").unwrap().round(), |
||||
Decimal::from(-2) |
||||
); |
||||
assert_eq!(Decimal::from(i64::MIN).round(), Decimal::from(i64::MIN)); |
||||
assert_eq!(Decimal::from(i64::MAX).round(), Decimal::from(i64::MAX)); |
||||
} |
||||
|
||||
#[test] |
||||
fn ceil() { |
||||
assert_eq!(Decimal::from_str("10").unwrap().ceil(), Decimal::from(10)); |
||||
assert_eq!(Decimal::from_str("-10").unwrap().ceil(), Decimal::from(-10)); |
||||
assert_eq!(Decimal::from_str("10.5").unwrap().ceil(), Decimal::from(11)); |
||||
assert_eq!( |
||||
Decimal::from_str("-10.5").unwrap().ceil(), |
||||
Decimal::from(-10) |
||||
); |
||||
assert_eq!(Decimal::from(i64::MIN).ceil(), Decimal::from(i64::MIN)); |
||||
assert_eq!(Decimal::from(i64::MAX).ceil(), Decimal::from(i64::MAX)); |
||||
} |
||||
|
||||
#[test] |
||||
fn floor() { |
||||
assert_eq!(Decimal::from_str("10").unwrap().ceil(), Decimal::from(10)); |
||||
assert_eq!(Decimal::from_str("-10").unwrap().ceil(), Decimal::from(-10)); |
||||
assert_eq!( |
||||
Decimal::from_str("10.5").unwrap().floor(), |
||||
Decimal::from(10) |
||||
); |
||||
assert_eq!( |
||||
Decimal::from_str("-10.5").unwrap().floor(), |
||||
Decimal::from(-11) |
||||
); |
||||
assert_eq!(Decimal::from(i64::MIN).floor(), Decimal::from(i64::MIN)); |
||||
assert_eq!(Decimal::from(i64::MAX).floor(), Decimal::from(i64::MAX)); |
||||
} |
||||
} |
@ -0,0 +1,3 @@ |
||||
mod decimal; |
||||
|
||||
pub use self::decimal::Decimal; |
Loading…
Reference in new issue