Fork of https://github.com/oxigraph/oxigraph.git for the purpose of NextGraph project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
oxigraph/lib/src/model/xsd/decimal.rs

694 lines
20 KiB

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 {
/// Constructs the decimal i / 10^n
#[allow(clippy::cast_possible_truncation)]
pub fn new(i: i128, n: u32) -> Result<Self, DecimalOverflowError> {
if n > DECIMAL_PART_DIGITS as u32 {
//TODO: check if end with zeros?
return Err(DecimalOverflowError);
}
Ok(Self {
value: i.checked_div(10_i128.pow(n)).ok_or(DecimalOverflowError)?,
})
}
#[inline]
pub fn from_be_bytes(bytes: [u8; 16]) -> Self {
Self {
value: i128::from_be_bytes(bytes),
}
}
#[inline]
pub fn to_be_bytes(self) -> [u8; 16] {
self.value.to_be_bytes()
}
/// [op:numeric-add](https://www.w3.org/TR/xpath-functions/#func-numeric-add)
#[inline]
pub fn checked_add(&self, rhs: impl Into<Self>) -> Option<Self> {
Some(Self {
value: self.value.checked_add(rhs.into().value)?,
})
}
/// [op:numeric-subtract](https://www.w3.org/TR/xpath-functions/#func-numeric-subtract)
#[inline]
pub fn checked_sub(&self, rhs: impl Into<Self>) -> Option<Self> {
Some(Self {
value: self.value.checked_sub(rhs.into().value)?,
})
}
/// [op:numeric-multiply](https://www.w3.org/TR/xpath-functions/#func-numeric-multiply)
#[inline]
pub fn checked_mul(&self, rhs: impl Into<Self>) -> Option<Self> {
//TODO: better algorithm to keep precision
Some(Self {
value: self
.value
.checked_div(DECIMAL_PART_HALF_POW)?
.checked_mul(rhs.into().value.checked_div(DECIMAL_PART_HALF_POW)?)?,
})
}
/// [op:numeric-divide](https://www.w3.org/TR/xpath-functions/#func-numeric-divide)
#[inline]
pub fn checked_div(&self, rhs: impl Into<Self>) -> Option<Self> {
//TODO: better algorithm to keep precision
Some(Self {
value: self
.value
.checked_mul(DECIMAL_PART_HALF_POW)?
.checked_div(rhs.into().value)?
.checked_mul(DECIMAL_PART_HALF_POW)?,
})
}
/// TODO: XSD? is well defined for not integer
pub fn checked_rem(&self, rhs: impl Into<Self>) -> Option<Self> {
Some(Self {
value: self.value.checked_rem(rhs.into().value)?,
})
}
pub fn checked_rem_euclid(&self, rhs: impl Into<Self>) -> Option<Self> {
Some(Self {
value: self.value.checked_rem_euclid(rhs.into().value)?,
})
}
/// [fn:abs](https://www.w3.org/TR/xpath-functions/#func-abs)
#[inline]
pub const fn abs(&self) -> Decimal {
Self {
value: self.value.abs(),
}
}
/// [fn:round](https://www.w3.org/TR/xpath-functions/#func-round)
#[inline]
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)
#[inline]
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)
#[inline]
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 const fn is_negative(&self) -> bool {
self.value < 0
}
pub const fn is_positive(&self) -> bool {
self.value > 0
}
/// Creates a `Decimal` from a `f32` without taking care of precision
#[inline]
pub(crate) fn from_f32(v: f32) -> Self {
Self::from_f64(v.into())
}
/// Creates a `f32` from a `Decimal` without taking care of precision
#[inline]
#[allow(clippy::cast_possible_truncation)]
pub fn to_f32(self) -> f32 {
self.to_f64() as f32
}
/// Creates a `Decimal` from a `f64` without taking care of precision
#[inline]
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
pub(crate) fn from_f64(v: f64) -> Self {
Self {
value: (v * (DECIMAL_PART_POW as f64)) as i128,
}
}
/// Creates a `f64` from a `Decimal` without taking care of precision
#[inline]
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
pub fn to_f64(self) -> f64 {
(self.value as f64) / (DECIMAL_PART_POW as f64)
}
pub(super) const fn as_i128(&self) -> i128 {
self.value / DECIMAL_PART_POW
}
#[cfg(test)]
pub(super) const fn min_value() -> Decimal {
Self {
value: i128::min_value(),
}
}
#[cfg(test)]
pub(super) const fn max_value() -> Decimal {
Self {
value: i128::max_value(),
}
}
#[cfg(test)]
pub(super) const fn step() -> Decimal {
Self { value: 1 }
}
}
impl From<i8> for Decimal {
#[inline]
fn from(value: i8) -> Self {
Self {
value: i128::from(value) * DECIMAL_PART_POW,
}
}
}
impl From<i16> for Decimal {
fn from(value: i16) -> Self {
Self {
value: i128::from(value) * DECIMAL_PART_POW,
}
}
}
impl From<i32> for Decimal {
fn from(value: i32) -> Self {
Self {
value: i128::from(value) * DECIMAL_PART_POW,
}
}
}
impl From<i64> for Decimal {
fn from(value: i64) -> Self {
Self {
value: i128::from(value) * DECIMAL_PART_POW,
}
}
}
impl From<u8> for Decimal {
fn from(value: u8) -> Self {
Self {
value: i128::from(value) * DECIMAL_PART_POW,
}
}
}
impl From<u16> for Decimal {
fn from(value: u16) -> Self {
Self {
value: i128::from(value) * DECIMAL_PART_POW,
}
}
}
impl From<u32> for Decimal {
fn from(value: u32) -> Self {
Self {
value: i128::from(value) * DECIMAL_PART_POW,
}
}
}
impl From<u64> for Decimal {
fn from(value: u64) -> Self {
Self {
value: i128::from(value) * DECIMAL_PART_POW,
}
}
}
impl TryFrom<i128> for Decimal {
type Error = DecimalOverflowError;
fn try_from(value: i128) -> Result<Self, DecimalOverflowError> {
Ok(Self {
value: value
.checked_mul(DECIMAL_PART_POW)
.ok_or(DecimalOverflowError)?,
})
}
}
impl TryFrom<u128> for Decimal {
type Error = DecimalOverflowError;
fn try_from(value: u128) -> Result<Self, DecimalOverflowError> {
Ok(Self {
value: i128::try_from(value)
.map_err(|_| DecimalOverflowError)?
.checked_mul(DECIMAL_PART_POW)
.ok_or(DecimalOverflowError)?,
})
}
}
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 = 0_i128;
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 From<DecimalOverflowError> for ParseDecimalError {
fn from(_: DecimalOverflowError) -> Self {
Self {
kind: ParseDecimalErrorKind::Overflow,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct DecimalOverflowError;
impl fmt::Display for DecimalOverflowError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Value overflow")
}
}
impl Error for DecimalOverflowError {}
impl fmt::Display for Decimal {
/// Formats the decimal following its canonical representation
#[allow(clippy::cast_possible_truncation)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.value == 0 {
return if let Some(width) = f.width() {
for _ in 0..width {
f.write_char('0')?;
}
Ok(())
} else {
f.write_char('0')
};
}
let mut value = self.value;
if self.value.is_negative() {
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;
}
let last_non_zero = i - 1;
let first_non_zero = digits
.iter()
.cloned()
.enumerate()
.find_map(|(i, v)| if v == b'0' { None } else { Some(i) })
.unwrap_or(40);
if last_non_zero >= DECIMAL_PART_DIGITS {
let end = if let Some(mut width) = f.width() {
if self.value.is_negative() {
width -= 1;
}
if last_non_zero - DECIMAL_PART_DIGITS + 1 < width {
DECIMAL_PART_DIGITS + width
} else {
last_non_zero + 1
}
} else {
last_non_zero + 1
};
for c in digits[DECIMAL_PART_DIGITS..end].iter().rev() {
f.write_char(char::from(*c))?;
}
} else {
f.write_char('0')?
}
if DECIMAL_PART_DIGITS > first_non_zero {
f.write_char('.')?;
let start = if let Some(precision) = f.precision() {
if DECIMAL_PART_DIGITS - first_non_zero > precision {
DECIMAL_PART_DIGITS - precision
} else {
first_non_zero
}
} else {
first_non_zero
};
for c in digits[start..DECIMAL_PART_DIGITS].iter().rev() {
f.write_char(char::from(*c))?;
}
}
Ok(())
}
}
impl Neg for Decimal {
type Output = Self;
#[inline]
fn neg(self) -> Self {
Self {
value: self.value.neg(),
}
}
}
impl TryFrom<Decimal> for i64 {
type Error = DecimalOverflowError;
fn try_from(value: Decimal) -> Result<i64, DecimalOverflowError> {
value
.value
.checked_div(DECIMAL_PART_POW)
.ok_or(DecimalOverflowError)?
.try_into()
.map_err(|_| DecimalOverflowError)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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(&Decimal::max_value().to_string()).unwrap(),
Decimal::max_value()
);
assert_eq!(
Decimal::from_str(
&Decimal::min_value()
.checked_add(Decimal::step())
.unwrap()
.to_string()
)
.unwrap(),
Decimal::min_value().checked_add(Decimal::step()).unwrap()
);
}
#[test]
fn format() {
assert_eq!(format!("{}", Decimal::from(0)), "0");
assert_eq!(format!("{}", Decimal::from(1)), "1");
assert_eq!(format!("{}", Decimal::from(10)), "10");
assert_eq!(format!("{}", Decimal::from(100)), "100");
assert_eq!(format!("{}", Decimal::from(-1)), "-1");
assert_eq!(format!("{}", Decimal::from(-10)), "-10");
assert_eq!(format!("{:02}", Decimal::from(0)), "00");
assert_eq!(format!("{:02}", Decimal::from(1)), "01");
assert_eq!(format!("{:02}", Decimal::from(10)), "10");
assert_eq!(format!("{:02}", Decimal::from(100)), "100");
assert_eq!(format!("{:02}", Decimal::from(-1)), "-1");
assert_eq!(format!("{:02}", Decimal::from(-10)), "-10");
}
#[test]
fn add() {
assert!(Decimal::min_value().checked_add(Decimal::step()).is_some());
assert!(Decimal::max_value().checked_add(Decimal::step()).is_none());
assert_eq!(
Decimal::max_value().checked_add(Decimal::min_value()),
Some(-Decimal::step())
);
}
#[test]
fn sub() {
assert!(Decimal::min_value().checked_sub(Decimal::step()).is_none());
assert!(Decimal::max_value().checked_sub(Decimal::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_value()).round(),
Decimal::from(i64::min_value())
);
assert_eq!(
Decimal::from(i64::max_value()).round(),
Decimal::from(i64::max_value())
);
}
#[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_value()).ceil(),
Decimal::from(i64::min_value())
);
assert_eq!(
Decimal::from(i64::max_value()).ceil(),
Decimal::from(i64::max_value())
);
}
#[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_value()).floor(),
Decimal::from(i64::min_value())
);
assert_eq!(
Decimal::from(i64::max_value()).floor(),
Decimal::from(i64::max_value())
);
}
}