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.
694 lines
20 KiB
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())
|
|
);
|
|
}
|
|
}
|
|
|