XSD: Improves arithmetic computations

Avoids internal overflow inside some decimal operations
pull/507/head
Tpt 2 years ago committed by Thomas Tanon
parent 8bec2e2ff9
commit d24461fc42
  1. 4
      lib/oxsdatatypes/src/boolean.rs
  2. 13
      lib/oxsdatatypes/src/date_time.rs
  3. 273
      lib/oxsdatatypes/src/decimal.rs
  4. 39
      lib/oxsdatatypes/src/double.rs
  5. 2
      lib/oxsdatatypes/src/duration.rs
  6. 45
      lib/oxsdatatypes/src/float.rs
  7. 41
      lib/oxsdatatypes/src/integer.rs
  8. 14
      lib/src/sparql/eval.rs

@ -43,14 +43,14 @@ impl From<Decimal> for Boolean {
impl From<Float> for Boolean { impl From<Float> for Boolean {
#[inline] #[inline]
fn from(value: Float) -> Self { fn from(value: Float) -> Self {
(value != Float::from(0.) && !value.is_naan()).into() (value != Float::from(0.) && !value.is_nan()).into()
} }
} }
impl From<Double> for Boolean { impl From<Double> for Boolean {
#[inline] #[inline]
fn from(value: Double) -> Self { fn from(value: Double) -> Self {
(value != Double::from(0.) && !value.is_naan()).into() (value != Double::from(0.) && !value.is_nan()).into()
} }
} }

@ -44,6 +44,7 @@ impl DateTime {
}) })
} }
/// [fn:current-dateTime](https://www.w3.org/TR/xpath-functions/#func-current-dateTime)
#[inline] #[inline]
pub fn now() -> Result<Self, DateTimeError> { pub fn now() -> Result<Self, DateTimeError> {
Ok(Self { Ok(Self {
@ -303,6 +304,12 @@ impl Time {
} }
} }
/// [fn:current-time](https://www.w3.org/TR/xpath-functions/#func-current-time)
#[inline]
pub fn now() -> Result<Self, DateTimeError> {
DateTime::now()?.try_into()
}
/// [fn:hour-from-time](https://www.w3.org/TR/xpath-functions/#func-hour-from-time) /// [fn:hour-from-time](https://www.w3.org/TR/xpath-functions/#func-hour-from-time)
#[inline] #[inline]
pub fn hour(&self) -> u8 { pub fn hour(&self) -> u8 {
@ -498,6 +505,12 @@ impl Date {
} }
} }
/// [fn:current-date](https://www.w3.org/TR/xpath-functions/#func-current-date)
#[inline]
pub fn now() -> Result<Self, DateTimeError> {
DateTime::now()?.try_into()
}
/// [fn:year-from-date](https://www.w3.org/TR/xpath-functions/#func-year-from-date) /// [fn:year-from-date](https://www.w3.org/TR/xpath-functions/#func-year-from-date)
#[inline] #[inline]
pub fn year(&self) -> i64 { pub fn year(&self) -> i64 {

@ -5,10 +5,9 @@ use std::fmt::Write;
use std::ops::Neg; use std::ops::Neg;
use std::str::FromStr; use std::str::FromStr;
const DECIMAL_PART_DIGITS: usize = 18; const DECIMAL_PART_DIGITS: u32 = 18;
const DECIMAL_PART_POW: i128 = 1_000_000_000_000_000_000; 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_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) /// [XML Schema `decimal` datatype](https://www.w3.org/TR/xmlschema11-2/#decimal)
/// ///
@ -22,10 +21,9 @@ pub struct Decimal {
impl Decimal { impl Decimal {
/// Constructs the decimal i / 10^n /// Constructs the decimal i / 10^n
#[allow(clippy::cast_possible_truncation)]
#[inline] #[inline]
pub fn new(i: i128, n: u32) -> Result<Self, DecimalOverflowError> { pub fn new(i: i128, n: u32) -> Result<Self, DecimalOverflowError> {
let shift = (DECIMAL_PART_DIGITS as u32) let shift = DECIMAL_PART_DIGITS
.checked_sub(n) .checked_sub(n)
.ok_or(DecimalOverflowError)?; .ok_or(DecimalOverflowError)?;
Ok(Self { Ok(Self {
@ -66,29 +64,69 @@ impl Decimal {
/// [op:numeric-multiply](https://www.w3.org/TR/xpath-functions/#func-numeric-multiply) /// [op:numeric-multiply](https://www.w3.org/TR/xpath-functions/#func-numeric-multiply)
#[inline] #[inline]
pub fn checked_mul(&self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_mul(&self, rhs: impl Into<Self>) -> Option<Self> {
//TODO: better algorithm to keep precision // Idea: we shift right as much as possible to keep as much precision as possible
// Do the multiplication and do the required left shift
let mut left = self.value;
let mut shift_left = 0_u32;
if left != 0 {
while left % 10 == 0 {
left /= 10;
shift_left += 1;
}
}
let mut right = rhs.into().value;
let mut shift_right = 0_u32;
if right != 0 {
while right % 10 == 0 {
right /= 10;
shift_right += 1;
}
}
// We do multiplication + shift
let shift = (shift_left + shift_right).checked_sub(DECIMAL_PART_DIGITS)?;
Some(Self { Some(Self {
value: self value: left
.value .checked_mul(right)?
.checked_div(DECIMAL_PART_HALF_POW)? .checked_mul(10_i128.checked_pow(shift)?)?,
.checked_mul(rhs.into().value.checked_div(DECIMAL_PART_HALF_POW)?)?,
}) })
} }
/// [op:numeric-divide](https://www.w3.org/TR/xpath-functions/#func-numeric-divide) /// [op:numeric-divide](https://www.w3.org/TR/xpath-functions/#func-numeric-divide)
#[inline] #[inline]
pub fn checked_div(&self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_div(&self, rhs: impl Into<Self>) -> Option<Self> {
//TODO: better algorithm to keep precision // Idea: we shift the dividend left as much as possible to keep as much precision as possible
// And we shift right the divisor as much as possible
// Do the multiplication and do the required shift
let mut left = self.value;
let mut shift_left = 0_u32;
if left != 0 {
while let Some(r) = left.checked_mul(10) {
assert_eq!(r / 10, left);
left = r;
shift_left += 1;
}
}
let mut right = rhs.into().value;
let mut shift_right = 0_u32;
if right != 0 {
while right % 10 == 0 {
right /= 10;
shift_right += 1;
}
}
// We do division + shift
let shift = (shift_left + shift_right).checked_sub(DECIMAL_PART_DIGITS)?;
Some(Self { Some(Self {
value: self value: left
.value .checked_div(right)?
.checked_mul(DECIMAL_PART_HALF_POW)? .checked_div(10_i128.checked_pow(shift)?)?,
.checked_div(rhs.into().value)?
.checked_mul(DECIMAL_PART_HALF_POW)?,
}) })
} }
/// TODO: XSD? is well defined for not integer /// [op:numeric-mod](https://www.w3.org/TR/xpath-functions/#func-numeric-mod)
#[inline] #[inline]
pub fn checked_rem(&self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_rem(&self, rhs: impl Into<Self>) -> Option<Self> {
Some(Self { Some(Self {
@ -174,9 +212,7 @@ impl Decimal {
pub const MAX: Self = Self { value: i128::MAX }; pub const MAX: Self = Self { value: i128::MAX };
#[cfg(test)] #[cfg(test)]
pub(super) const fn step() -> Self { pub const STEP: Self = Self { value: 1 };
Self { value: 1 }
}
} }
impl From<bool> for Decimal { impl From<bool> for Decimal {
@ -316,13 +352,10 @@ impl TryFrom<Double> for Decimal {
#[inline] #[inline]
#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)] #[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
fn try_from(value: Double) -> Result<Self, DecimalOverflowError> { fn try_from(value: Double) -> Result<Self, DecimalOverflowError> {
let shifted = value * Double::from(DECIMAL_PART_POW as f64); let shifted = f64::from(value) * (DECIMAL_PART_POW as f64);
if shifted.is_finite() if shifted.is_finite() && (i128::MIN as f64) <= shifted && shifted <= (i128::MAX as f64) {
&& Double::from(i128::MIN as f64) <= shifted
&& shifted <= Double::from(i128::MAX as f64)
{
Ok(Self { Ok(Self {
value: f64::from(shifted) as i128, value: shifted as i128,
}) })
} else { } else {
Err(DecimalOverflowError) Err(DecimalOverflowError)
@ -334,7 +367,7 @@ impl From<Decimal> for Float {
#[inline] #[inline]
#[allow(clippy::cast_precision_loss)] #[allow(clippy::cast_precision_loss)]
fn from(value: Decimal) -> Self { fn from(value: Decimal) -> Self {
((value.value as f32) / (DECIMAL_PART_POW as f32)).into() Double::from(value).into()
} }
} }
@ -342,7 +375,18 @@ impl From<Decimal> for Double {
#[inline] #[inline]
#[allow(clippy::cast_precision_loss)] #[allow(clippy::cast_precision_loss)]
fn from(value: Decimal) -> Self { fn from(value: Decimal) -> Self {
((value.value as f64) / (DECIMAL_PART_POW as f64)).into() let mut value = value.value;
let mut shift = DECIMAL_PART_POW;
// Hack to improve precision
if value != 0 {
while shift != 1 && value % 10 == 0 {
value /= 10;
shift /= 10;
}
}
((value as f64) / (shift as f64)).into()
} }
} }
@ -374,8 +418,8 @@ impl FromStr for Decimal {
} }
let (sign, mut input) = match input.first() { let (sign, mut input) = match input.first() {
Some(b'+') => (1, &input[1..]), Some(b'+') => (1_i128, &input[1..]),
Some(b'-') => (-1, &input[1..]), Some(b'-') => (-1_i128, &input[1..]),
_ => (1, input), _ => (1, input),
}; };
@ -386,7 +430,7 @@ impl FromStr for Decimal {
value = value value = value
.checked_mul(10) .checked_mul(10)
.ok_or(PARSE_OVERFLOW)? .ok_or(PARSE_OVERFLOW)?
.checked_add((*c - b'0').into()) .checked_add(sign * i128::from(*c - b'0'))
.ok_or(PARSE_OVERFLOW)?; .ok_or(PARSE_OVERFLOW)?;
input = &input[1..]; input = &input[1..];
} else { } else {
@ -414,7 +458,7 @@ impl FromStr for Decimal {
value = value value = value
.checked_mul(10) .checked_mul(10)
.ok_or(PARSE_OVERFLOW)? .ok_or(PARSE_OVERFLOW)?
.checked_add((*c - b'0').into()) .checked_add(sign * i128::from(*c - b'0'))
.ok_or(PARSE_OVERFLOW)?; .ok_or(PARSE_OVERFLOW)?;
input = &input[1..]; input = &input[1..];
} else { } else {
@ -431,11 +475,7 @@ impl FromStr for Decimal {
} }
Ok(Self { Ok(Self {
value: value value: value.checked_mul(exp).ok_or(PARSE_OVERFLOW)?,
.checked_mul(sign)
.ok_or(PARSE_OVERFLOW)?
.checked_mul(exp)
.ok_or(PARSE_OVERFLOW)?,
}) })
} }
} }
@ -476,37 +516,38 @@ impl fmt::Display for Decimal {
.find_map(|(i, v)| if v == b'0' { None } else { Some(i) }) .find_map(|(i, v)| if v == b'0' { None } else { Some(i) })
.unwrap_or(40); .unwrap_or(40);
if last_non_zero >= DECIMAL_PART_DIGITS { let decimal_part_digits = usize::try_from(DECIMAL_PART_DIGITS).unwrap();
if last_non_zero >= decimal_part_digits {
let end = if let Some(mut width) = f.width() { let end = if let Some(mut width) = f.width() {
if self.value.is_negative() { if self.value.is_negative() {
width -= 1; width -= 1;
} }
if last_non_zero - DECIMAL_PART_DIGITS + 1 < width { if last_non_zero - decimal_part_digits + 1 < width {
DECIMAL_PART_DIGITS + width decimal_part_digits + width
} else { } else {
last_non_zero + 1 last_non_zero + 1
} }
} else { } else {
last_non_zero + 1 last_non_zero + 1
}; };
for c in digits[DECIMAL_PART_DIGITS..end].iter().rev() { for c in digits[decimal_part_digits..end].iter().rev() {
f.write_char(char::from(*c))?; f.write_char(char::from(*c))?;
} }
} else { } else {
f.write_char('0')? f.write_char('0')?
} }
if DECIMAL_PART_DIGITS > first_non_zero { if decimal_part_digits > first_non_zero {
f.write_char('.')?; f.write_char('.')?;
let start = if let Some(precision) = f.precision() { let start = if let Some(precision) = f.precision() {
if DECIMAL_PART_DIGITS - first_non_zero > precision { if decimal_part_digits - first_non_zero > precision {
DECIMAL_PART_DIGITS - precision decimal_part_digits - precision
} else { } else {
first_non_zero first_non_zero
} }
} else { } else {
first_non_zero first_non_zero
}; };
for c in digits[start..DECIMAL_PART_DIGITS].iter().rev() { for c in digits[start..decimal_part_digits].iter().rev() {
f.write_char(char::from(*c))?; f.write_char(char::from(*c))?;
} }
} }
@ -626,15 +667,7 @@ mod tests {
assert_eq!(Decimal::from_str("0")?.to_string(), "0"); assert_eq!(Decimal::from_str("0")?.to_string(), "0");
assert_eq!(Decimal::from_str("-0")?.to_string(), "0"); assert_eq!(Decimal::from_str("-0")?.to_string(), "0");
assert_eq!(Decimal::from_str(&Decimal::MAX.to_string())?, Decimal::MAX); assert_eq!(Decimal::from_str(&Decimal::MAX.to_string())?, Decimal::MAX);
assert_eq!( assert_eq!(Decimal::from_str(&Decimal::MIN.to_string())?, Decimal::MIN);
Decimal::from_str(
&Decimal::MIN
.checked_add(Decimal::step())
.unwrap()
.to_string()
)?,
Decimal::MIN.checked_add(Decimal::step()).unwrap()
);
assert!(Decimal::from_str("0.0000000000000000001").is_err()); assert!(Decimal::from_str("0.0000000000000000001").is_err());
assert!(Decimal::from_str("1000000000000000000000").is_err()); assert!(Decimal::from_str("1000000000000000000000").is_err());
assert_eq!( assert_eq!(
@ -663,58 +696,98 @@ mod tests {
#[test] #[test]
fn add() { fn add() {
assert!(Decimal::MIN.checked_add(Decimal::step()).is_some()); assert!(Decimal::MIN.checked_add(Decimal::STEP).is_some());
assert!(Decimal::MAX.checked_add(Decimal::step()).is_none()); assert!(Decimal::MAX.checked_add(Decimal::STEP).is_none());
assert_eq!( assert_eq!(Decimal::MAX.checked_add(Decimal::MIN), Some(-Decimal::STEP));
Decimal::MAX.checked_add(Decimal::MIN),
Some(-Decimal::step())
);
} }
#[test] #[test]
fn sub() { fn sub() {
assert!(Decimal::MIN.checked_sub(Decimal::step()).is_none()); assert!(Decimal::MIN.checked_sub(Decimal::STEP).is_none());
assert!(Decimal::MAX.checked_sub(Decimal::step()).is_some()); assert!(Decimal::MAX.checked_sub(Decimal::STEP).is_some());
} }
#[test] #[test]
fn mul() -> Result<(), ParseDecimalError> { fn mul() -> Result<(), ParseDecimalError> {
assert_eq!(Decimal::from(1).checked_mul(-1), Some(Decimal::from(-1)));
assert_eq!( assert_eq!(
Decimal::from_str("1")?.checked_mul(Decimal::from_str("-1")?), Decimal::from(1000).checked_mul(1000),
Some(Decimal::from_str("-1")?) Some(Decimal::from(1000000))
);
assert_eq!(
Decimal::from_str("1000")?.checked_mul(Decimal::from_str("1000")?),
Some(Decimal::from_str("1000000")?)
); );
assert_eq!( assert_eq!(
Decimal::from_str("0.1")?.checked_mul(Decimal::from_str("0.01")?), Decimal::from_str("0.1")?.checked_mul(Decimal::from_str("0.01")?),
Some(Decimal::from_str("0.001")?) Some(Decimal::from_str("0.001")?)
); );
assert_eq!(Decimal::from(0).checked_mul(1), Some(Decimal::from(0)));
assert_eq!(Decimal::from(1).checked_mul(0), Some(Decimal::from(0)));
assert_eq!(Decimal::MAX.checked_mul(1), Some(Decimal::MAX));
assert_eq!(Decimal::MIN.checked_mul(1), Some(Decimal::MIN));
assert_eq!(
Decimal::from(1).checked_mul(Decimal::MAX),
Some(Decimal::MAX)
);
assert_eq!(
Decimal::from(1).checked_mul(Decimal::MIN),
Some(Decimal::MIN)
);
assert_eq!(
Decimal::MAX.checked_mul(-1),
Some(Decimal::MIN.checked_add(Decimal::STEP).unwrap())
);
assert_eq!(Decimal::MIN.checked_mul(-1), None);
assert_eq!(
Decimal::MIN
.checked_add(Decimal::STEP)
.unwrap()
.checked_mul(-1),
Some(Decimal::MAX)
);
Ok(()) Ok(())
} }
#[test] #[test]
fn div() -> Result<(), ParseDecimalError> { fn div() -> Result<(), ParseDecimalError> {
assert_eq!(Decimal::from(1).checked_div(1), Some(Decimal::from(1)));
assert_eq!(Decimal::from(100).checked_div(10), Some(Decimal::from(10)));
assert_eq!( assert_eq!(
Decimal::from_str("1")?.checked_div(Decimal::from_str("1")?), Decimal::from(10).checked_div(100),
Some(Decimal::from_str("1")?) Some(Decimal::from_str("0.1")?)
); );
assert_eq!(Decimal::from(1).checked_div(0), None);
assert_eq!(Decimal::from(0).checked_div(1), Some(Decimal::from(0)));
assert_eq!(Decimal::MAX.checked_div(1), Some(Decimal::MAX));
assert_eq!(Decimal::MIN.checked_div(1), Some(Decimal::MIN));
assert_eq!( assert_eq!(
Decimal::from_str("100")?.checked_div(Decimal::from_str("10")?), Decimal::MAX.checked_div(-1),
Some(Decimal::from_str("10")?) Some(Decimal::MIN.checked_add(Decimal::STEP).unwrap())
); );
assert_eq!(Decimal::MIN.checked_div(-1), None);
assert_eq!( assert_eq!(
Decimal::from_str("10")?.checked_div(Decimal::from_str("100")?), Decimal::MIN
Some(Decimal::from_str("0.1")?) .checked_add(Decimal::STEP)
.unwrap()
.checked_div(-1),
Some(Decimal::MAX)
);
Ok(())
}
#[test]
fn rem() -> Result<(), ParseDecimalError> {
assert_eq!(Decimal::from(10).checked_rem(3), Some(Decimal::from(1)));
assert_eq!(Decimal::from(6).checked_rem(-2), Some(Decimal::from(0)));
assert_eq!(
Decimal::from_str("4.5")?.checked_rem(Decimal::from_str("1.2")?),
Some(Decimal::from_str("0.9")?)
); );
assert_eq!(Decimal::from(1).checked_rem(0), None);
Ok(()) Ok(())
} }
#[test] #[test]
fn round() -> Result<(), ParseDecimalError> { fn round() -> Result<(), ParseDecimalError> {
assert_eq!(Decimal::from_str("10")?.round(), Decimal::from(10)); assert_eq!(Decimal::from(10).round(), Decimal::from(10));
assert_eq!(Decimal::from_str("-10")?.round(), Decimal::from(-10)); assert_eq!(Decimal::from(-10).round(), Decimal::from(-10));
assert_eq!(Decimal::from_str("2.5")?.round(), Decimal::from(3)); assert_eq!(Decimal::from_str("2.5")?.round(), Decimal::from(3));
assert_eq!(Decimal::from_str("2.4999")?.round(), Decimal::from(2)); assert_eq!(Decimal::from_str("2.4999")?.round(), Decimal::from(2));
assert_eq!(Decimal::from_str("-2.5")?.round(), Decimal::from(-2)); assert_eq!(Decimal::from_str("-2.5")?.round(), Decimal::from(-2));
@ -725,8 +798,8 @@ mod tests {
#[test] #[test]
fn ceil() -> Result<(), ParseDecimalError> { fn ceil() -> Result<(), ParseDecimalError> {
assert_eq!(Decimal::from_str("10")?.ceil(), Decimal::from(10)); assert_eq!(Decimal::from(10).ceil(), Decimal::from(10));
assert_eq!(Decimal::from_str("-10")?.ceil(), Decimal::from(-10)); assert_eq!(Decimal::from(-10).ceil(), Decimal::from(-10));
assert_eq!(Decimal::from_str("10.5")?.ceil(), Decimal::from(11)); assert_eq!(Decimal::from_str("10.5")?.ceil(), Decimal::from(11));
assert_eq!(Decimal::from_str("-10.5")?.ceil(), Decimal::from(-10)); assert_eq!(Decimal::from_str("-10.5")?.ceil(), Decimal::from(-10));
assert_eq!(Decimal::from(i64::MIN).ceil(), Decimal::from(i64::MIN)); assert_eq!(Decimal::from(i64::MIN).ceil(), Decimal::from(i64::MIN));
@ -736,8 +809,8 @@ mod tests {
#[test] #[test]
fn floor() -> Result<(), ParseDecimalError> { fn floor() -> Result<(), ParseDecimalError> {
assert_eq!(Decimal::from_str("10")?.ceil(), Decimal::from(10)); assert_eq!(Decimal::from(10).ceil(), Decimal::from(10));
assert_eq!(Decimal::from_str("-10")?.ceil(), Decimal::from(-10)); assert_eq!(Decimal::from(-10).ceil(), Decimal::from(-10));
assert_eq!(Decimal::from_str("10.5")?.floor(), Decimal::from(10)); assert_eq!(Decimal::from_str("10.5")?.floor(), Decimal::from(10));
assert_eq!(Decimal::from_str("-10.5")?.floor(), Decimal::from(-11)); assert_eq!(Decimal::from_str("-10.5")?.floor(), Decimal::from(-11));
assert_eq!(Decimal::from(i64::MIN).floor(), Decimal::from(i64::MIN)); assert_eq!(Decimal::from(i64::MIN).floor(), Decimal::from(i64::MIN));
@ -780,11 +853,11 @@ mod tests {
fn from_float() -> Result<(), ParseDecimalError> { fn from_float() -> Result<(), ParseDecimalError> {
assert_eq!( assert_eq!(
Decimal::try_from(Float::from(0.)).ok(), Decimal::try_from(Float::from(0.)).ok(),
Some(Decimal::from_str("0")?) Some(Decimal::from(0))
); );
assert_eq!( assert_eq!(
Decimal::try_from(Float::from(-0.)).ok(), Decimal::try_from(Float::from(-0.)).ok(),
Some(Decimal::from_str("0.")?) Some(Decimal::from(0))
); );
assert_eq!( assert_eq!(
Decimal::try_from(Float::from(-123.5)).ok(), Decimal::try_from(Float::from(-123.5)).ok(),
@ -798,10 +871,10 @@ mod tests {
assert!( assert!(
Decimal::try_from(Float::from(1_672_507_302_466.)) Decimal::try_from(Float::from(1_672_507_302_466.))
.unwrap() .unwrap()
.checked_sub(Decimal::from_str("1672507302466")?) .checked_sub(Decimal::from(1_672_507_293_696_i64))
.unwrap() .unwrap()
.abs() .abs()
< Decimal::from(1_000_000) < Decimal::from(1)
); );
Ok(()) Ok(())
} }
@ -810,11 +883,11 @@ mod tests {
fn from_double() -> Result<(), ParseDecimalError> { fn from_double() -> Result<(), ParseDecimalError> {
assert_eq!( assert_eq!(
Decimal::try_from(Double::from(0.)).ok(), Decimal::try_from(Double::from(0.)).ok(),
Some(Decimal::from_str("0")?) Some(Decimal::from(0))
); );
assert_eq!( assert_eq!(
Decimal::try_from(Double::from(-0.)).ok(), Decimal::try_from(Double::from(-0.)).ok(),
Some(Decimal::from_str("0")?) Some(Decimal::from(0))
); );
assert_eq!( assert_eq!(
Decimal::try_from(Double::from(-123.1)).ok(), Decimal::try_from(Double::from(-123.1)).ok(),
@ -823,7 +896,7 @@ mod tests {
assert!( assert!(
Decimal::try_from(Double::from(1_672_507_302_466.)) Decimal::try_from(Double::from(1_672_507_302_466.))
.unwrap() .unwrap()
.checked_sub(Decimal::from_str("1672507302466")?) .checked_sub(Decimal::from(1_672_507_302_466_i64))
.unwrap() .unwrap()
.abs() .abs()
< Decimal::from(1) < Decimal::from(1)
@ -836,6 +909,34 @@ mod tests {
Ok(()) Ok(())
} }
#[test]
fn to_float() -> Result<(), ParseDecimalError> {
assert_eq!(Float::from(Decimal::from(0)), Float::from(0.));
assert_eq!(Float::from(Decimal::from(1)), Float::from(1.));
assert_eq!(Float::from(Decimal::from(10)), Float::from(10.));
assert_eq!(Float::from(Decimal::from_str("0.1")?), Float::from(0.1));
assert!((Float::from(Decimal::MAX) - Float::from(1.701412e20)).abs() < Float::from(1.));
assert!((Float::from(Decimal::MIN) - Float::from(-1.701412e20)).abs() < Float::from(1.));
Ok(())
}
#[test]
fn to_double() -> Result<(), ParseDecimalError> {
assert_eq!(Double::from(Decimal::from(0)), Double::from(0.));
assert_eq!(Double::from(Decimal::from(1)), Double::from(1.));
assert_eq!(Double::from(Decimal::from(10)), Double::from(10.));
assert_eq!(Double::from(Decimal::from_str("0.1")?), Double::from(0.1));
assert!(
(Double::from(Decimal::MAX) - Double::from(1.7014118346046924e20)).abs()
< Double::from(1.)
);
assert!(
(Double::from(Decimal::MIN) - Double::from(-1.7014118346046924e20)).abs()
< Double::from(1.)
);
Ok(())
}
#[test] #[test]
fn minimally_conformant() -> Result<(), ParseDecimalError> { fn minimally_conformant() -> Result<(), ParseDecimalError> {
// All minimally conforming processors must support decimal values whose absolute value can be expressed as i / 10^k, // All minimally conforming processors must support decimal values whose absolute value can be expressed as i / 10^k,

@ -53,6 +53,12 @@ impl Double {
self.value.round().into() self.value.round().into()
} }
#[inline]
pub fn is_nan(self) -> bool {
self.value.is_nan()
}
#[deprecated(note = "Use .is_nan()")]
#[inline] #[inline]
pub fn is_naan(self) -> bool { pub fn is_naan(self) -> bool {
self.value.is_nan() self.value.is_nan()
@ -68,6 +74,20 @@ impl Double {
pub fn is_identical_with(&self, other: &Self) -> bool { pub fn is_identical_with(&self, other: &Self) -> bool {
self.value.to_ne_bytes() == other.value.to_ne_bytes() self.value.to_ne_bytes() == other.value.to_ne_bytes()
} }
pub const MIN: Self = Self { value: f64::MIN };
pub const MAX: Self = Self { value: f64::MAX };
pub const INFINITY: Self = Self {
value: f64::INFINITY,
};
pub const NEG_INFINITY: Self = Self {
value: f64::NEG_INFINITY,
};
pub const NAN: Self = Self { value: f64::NAN };
} }
impl From<Double> for f64 { impl From<Double> for f64 {
@ -243,7 +263,7 @@ mod tests {
#[test] #[test]
fn eq() { fn eq() {
assert_eq!(Double::from(0_f64), Double::from(0_f64)); assert_eq!(Double::from(0_f64), Double::from(0_f64));
assert_ne!(Double::from(f64::NAN), Double::from(f64::NAN)); assert_ne!(Double::NAN, Double::NAN);
assert_eq!(Double::from(-0.), Double::from(0.)); assert_eq!(Double::from(-0.), Double::from(0.));
} }
@ -254,18 +274,15 @@ mod tests {
Some(Ordering::Equal) Some(Ordering::Equal)
); );
assert_eq!( assert_eq!(
Double::from(f64::INFINITY).partial_cmp(&Double::from(f64::MAX)), Double::INFINITY.partial_cmp(&Double::MAX),
Some(Ordering::Greater) Some(Ordering::Greater)
); );
assert_eq!( assert_eq!(
Double::from(f64::NEG_INFINITY).partial_cmp(&Double::from(f64::MIN)), Double::NEG_INFINITY.partial_cmp(&Double::MIN),
Some(Ordering::Less) Some(Ordering::Less)
); );
assert_eq!(Double::from(f64::NAN).partial_cmp(&Double::from(0.)), None); assert_eq!(Double::NAN.partial_cmp(&Double::from(0.)), None);
assert_eq!( assert_eq!(Double::NAN.partial_cmp(&Double::NAN), None);
Double::from(f64::NAN).partial_cmp(&Double::from(f64::NAN)),
None
);
assert_eq!( assert_eq!(
Double::from(0.).partial_cmp(&Double::from(-0.)), Double::from(0.).partial_cmp(&Double::from(-0.)),
Some(Ordering::Equal) Some(Ordering::Equal)
@ -275,7 +292,7 @@ mod tests {
#[test] #[test]
fn is_identical_with() { fn is_identical_with() {
assert!(Double::from(0.).is_identical_with(&Double::from(0.))); assert!(Double::from(0.).is_identical_with(&Double::from(0.)));
assert!(Double::from(f64::NAN).is_identical_with(&Double::from(f64::NAN))); assert!(Double::NAN.is_identical_with(&Double::NAN));
assert!(!Double::from(-0.).is_identical_with(&Double::from(0.))); assert!(!Double::from(-0.).is_identical_with(&Double::from(0.)));
} }
@ -297,11 +314,11 @@ mod tests {
assert_eq!(Double::from_str("-1.")?.to_string(), "-1"); assert_eq!(Double::from_str("-1.")?.to_string(), "-1");
assert_eq!( assert_eq!(
Double::from_str(&f64::MIN.to_string()).unwrap(), Double::from_str(&f64::MIN.to_string()).unwrap(),
Double::from(f64::MIN) Double::MIN
); );
assert_eq!( assert_eq!(
Double::from_str(&f64::MAX.to_string()).unwrap(), Double::from_str(&f64::MAX.to_string()).unwrap(),
Double::from(f64::MAX) Double::MAX
); );
Ok(()) Ok(())
} }

@ -601,7 +601,7 @@ mod tests {
fn from_str() -> Result<(), XsdParseError> { fn from_str() -> Result<(), XsdParseError> {
let min = Duration::new( let min = Duration::new(
i64::MIN + 1, i64::MIN + 1,
Decimal::MIN.checked_add(Decimal::step()).unwrap(), Decimal::MIN.checked_add(Decimal::STEP).unwrap(),
); );
let max = Duration::new(i64::MAX, Decimal::MAX); let max = Duration::new(i64::MAX, Decimal::MAX);

@ -53,11 +53,17 @@ impl Float {
self.value.round().into() self.value.round().into()
} }
#[deprecated(note = "Use .is_nan()")]
#[inline] #[inline]
pub fn is_naan(self) -> bool { pub fn is_naan(self) -> bool {
self.value.is_nan() self.value.is_nan()
} }
#[inline]
pub fn is_nan(self) -> bool {
self.value.is_nan()
}
#[inline] #[inline]
pub fn is_finite(self) -> bool { pub fn is_finite(self) -> bool {
self.value.is_finite() self.value.is_finite()
@ -68,6 +74,20 @@ impl Float {
pub fn is_identical_with(&self, other: &Self) -> bool { pub fn is_identical_with(&self, other: &Self) -> bool {
self.value.to_ne_bytes() == other.value.to_ne_bytes() self.value.to_ne_bytes() == other.value.to_ne_bytes()
} }
pub const MIN: Self = Self { value: f32::MIN };
pub const MAX: Self = Self { value: f32::MAX };
pub const INFINITY: Self = Self {
value: f32::INFINITY,
};
pub const NEG_INFINITY: Self = Self {
value: f32::NEG_INFINITY,
};
pub const NAN: Self = Self { value: f32::NAN };
} }
impl From<Float> for f32 { impl From<Float> for f32 {
@ -233,7 +253,7 @@ mod tests {
#[test] #[test]
fn eq() { fn eq() {
assert_eq!(Float::from(0.), Float::from(0.)); assert_eq!(Float::from(0.), Float::from(0.));
assert_ne!(Float::from(f32::NAN), Float::from(f32::NAN)); assert_ne!(Float::NAN, Float::NAN);
assert_eq!(Float::from(-0.), Float::from(0.)); assert_eq!(Float::from(-0.), Float::from(0.));
} }
@ -244,18 +264,15 @@ mod tests {
Some(Ordering::Equal) Some(Ordering::Equal)
); );
assert_eq!( assert_eq!(
Float::from(f32::INFINITY).partial_cmp(&Float::from(f32::MAX)), Float::INFINITY.partial_cmp(&Float::MAX),
Some(Ordering::Greater) Some(Ordering::Greater)
); );
assert_eq!( assert_eq!(
Float::from(f32::NEG_INFINITY).partial_cmp(&Float::from(f32::MIN)), Float::NEG_INFINITY.partial_cmp(&Float::MIN),
Some(Ordering::Less) Some(Ordering::Less)
); );
assert_eq!(Float::from(f32::NAN).partial_cmp(&Float::from(0.)), None); assert_eq!(Float::NAN.partial_cmp(&Float::from(0.)), None);
assert_eq!( assert_eq!(Float::NAN.partial_cmp(&Float::NAN), None);
Float::from(f32::NAN).partial_cmp(&Float::from(f32::NAN)),
None
);
assert_eq!( assert_eq!(
Float::from(0.).partial_cmp(&Float::from(-0.)), Float::from(0.).partial_cmp(&Float::from(-0.)),
Some(Ordering::Equal) Some(Ordering::Equal)
@ -265,7 +282,7 @@ mod tests {
#[test] #[test]
fn is_identical_with() { fn is_identical_with() {
assert!(Float::from(0.).is_identical_with(&Float::from(0.))); assert!(Float::from(0.).is_identical_with(&Float::from(0.)));
assert!(Float::from(f32::NAN).is_identical_with(&Float::from(f32::NAN))); assert!(Float::NAN.is_identical_with(&Float::NAN));
assert!(!Float::from(-0.).is_identical_with(&Float::from(0.))); assert!(!Float::from(-0.).is_identical_with(&Float::from(0.)));
} }
@ -285,14 +302,8 @@ mod tests {
assert_eq!(Float::from_str("-1")?.to_string(), "-1"); assert_eq!(Float::from_str("-1")?.to_string(), "-1");
assert_eq!(Float::from_str("1.")?.to_string(), "1"); assert_eq!(Float::from_str("1.")?.to_string(), "1");
assert_eq!(Float::from_str("-1.")?.to_string(), "-1"); assert_eq!(Float::from_str("-1.")?.to_string(), "-1");
assert_eq!( assert_eq!(Float::from_str(&f32::MIN.to_string())?, Float::MIN);
Float::from_str(&f32::MIN.to_string())?, assert_eq!(Float::from_str(&f32::MAX.to_string())?, Float::MAX);
Float::from(f32::MIN)
);
assert_eq!(
Float::from_str(&f32::MAX.to_string())?,
Float::from(f32::MAX)
);
Ok(()) Ok(())
} }
} }

@ -58,6 +58,7 @@ impl Integer {
}) })
} }
/// [op:numeric-mod](https://www.w3.org/TR/xpath-functions/#func-numeric-mod)
#[inline] #[inline]
pub fn checked_rem(&self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_rem(&self, rhs: impl Into<Self>) -> Option<Self> {
Some(Self { Some(Self {
@ -95,6 +96,10 @@ impl Integer {
pub fn is_identical_with(&self, other: &Self) -> bool { pub fn is_identical_with(&self, other: &Self) -> bool {
self == other self == other
} }
pub const MIN: Self = Self { value: i64::MIN };
pub const MAX: Self = Self { value: i64::MAX };
} }
impl From<bool> for Integer { impl From<bool> for Integer {
@ -312,4 +317,40 @@ mod tests {
assert!(Integer::try_from(Decimal::MAX).is_err()); assert!(Integer::try_from(Decimal::MAX).is_err());
Ok(()) Ok(())
} }
#[test]
fn add() {
assert_eq!(
Integer::MIN.checked_add(1),
Some(Integer::from(i64::MIN + 1))
);
assert_eq!(Integer::MAX.checked_add(1), None);
}
#[test]
fn sub() {
assert_eq!(Integer::MIN.checked_sub(1), None);
assert_eq!(
Integer::MAX.checked_sub(1),
Some(Integer::from(i64::MAX - 1))
);
}
#[test]
fn mul() {
assert_eq!(Integer::MIN.checked_mul(2), None);
assert_eq!(Integer::MAX.checked_mul(2), None);
}
#[test]
fn div() {
assert_eq!(Integer::from(1).checked_div(0), None);
}
#[test]
fn rem() {
assert_eq!(Integer::from(10).checked_rem(3), Some(Integer::from(1)));
assert_eq!(Integer::from(6).checked_rem(-2), Some(Integer::from(0)));
assert_eq!(Integer::from(1).checked_rem(0), None);
}
} }

@ -930,7 +930,6 @@ impl SimpleEvaluator {
} }
} }
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
fn expression_evaluator( fn expression_evaluator(
&self, &self,
expression: &PlanExpression, expression: &PlanExpression,
@ -1426,7 +1425,11 @@ impl SimpleEvaluator {
let arg = self.expression_evaluator(arg, stat_children); let arg = self.expression_evaluator(arg, stat_children);
let dataset = Rc::clone(&self.dataset); let dataset = Rc::clone(&self.dataset);
Rc::new(move |tuple| { Rc::new(move |tuple| {
Some((to_string(&dataset, &arg(tuple)?)?.chars().count() as i64).into()) Some(
i64::try_from(to_string(&dataset, &arg(tuple)?)?.chars().count())
.ok()?
.into(),
)
}) })
} }
PlanExpression::StaticReplace(arg, regex, replacement) => { PlanExpression::StaticReplace(arg, regex, replacement) => {
@ -2383,11 +2386,6 @@ fn encode_bindings(
})) }))
} }
#[allow(
clippy::float_cmp,
clippy::cast_possible_truncation,
clippy::cast_precision_loss
)]
fn equals(a: &EncodedTerm, b: &EncodedTerm) -> Option<bool> { fn equals(a: &EncodedTerm, b: &EncodedTerm) -> Option<bool> {
match a { match a {
EncodedTerm::DefaultGraph EncodedTerm::DefaultGraph
@ -2655,7 +2653,6 @@ fn partial_cmp(dataset: &DatasetView, a: &EncodedTerm, b: &EncodedTerm) -> Optio
} }
} }
#[allow(clippy::cast_precision_loss)]
fn partial_cmp_literals( fn partial_cmp_literals(
dataset: &DatasetView, dataset: &DatasetView,
a: &EncodedTerm, a: &EncodedTerm,
@ -2913,7 +2910,6 @@ enum NumericBinaryOperands {
} }
impl NumericBinaryOperands { impl NumericBinaryOperands {
#[allow(clippy::cast_precision_loss)]
fn new(a: EncodedTerm, b: EncodedTerm) -> Option<Self> { fn new(a: EncodedTerm, b: EncodedTerm) -> Option<Self> {
match (a, b) { match (a, b) {
(EncodedTerm::FloatLiteral(v1), EncodedTerm::FloatLiteral(v2)) => { (EncodedTerm::FloatLiteral(v1), EncodedTerm::FloatLiteral(v2)) => {

Loading…
Cancel
Save