From d24461fc4245a2f3778adbd2ddb53fbc7494c474 Mon Sep 17 00:00:00 2001 From: Tpt Date: Fri, 19 May 2023 22:22:20 +0200 Subject: [PATCH] XSD: Improves arithmetic computations Avoids internal overflow inside some decimal operations --- lib/oxsdatatypes/src/boolean.rs | 4 +- lib/oxsdatatypes/src/date_time.rs | 13 ++ lib/oxsdatatypes/src/decimal.rs | 273 ++++++++++++++++++++---------- lib/oxsdatatypes/src/double.rs | 39 +++-- lib/oxsdatatypes/src/duration.rs | 2 +- lib/oxsdatatypes/src/float.rs | 45 +++-- lib/oxsdatatypes/src/integer.rs | 41 +++++ lib/src/sparql/eval.rs | 14 +- 8 files changed, 305 insertions(+), 126 deletions(-) diff --git a/lib/oxsdatatypes/src/boolean.rs b/lib/oxsdatatypes/src/boolean.rs index 9544cac9..fd213a90 100644 --- a/lib/oxsdatatypes/src/boolean.rs +++ b/lib/oxsdatatypes/src/boolean.rs @@ -43,14 +43,14 @@ impl From for Boolean { impl From for Boolean { #[inline] fn from(value: Float) -> Self { - (value != Float::from(0.) && !value.is_naan()).into() + (value != Float::from(0.) && !value.is_nan()).into() } } impl From for Boolean { #[inline] fn from(value: Double) -> Self { - (value != Double::from(0.) && !value.is_naan()).into() + (value != Double::from(0.) && !value.is_nan()).into() } } diff --git a/lib/oxsdatatypes/src/date_time.rs b/lib/oxsdatatypes/src/date_time.rs index c7fe48e8..63c45f0b 100644 --- a/lib/oxsdatatypes/src/date_time.rs +++ b/lib/oxsdatatypes/src/date_time.rs @@ -44,6 +44,7 @@ impl DateTime { }) } + /// [fn:current-dateTime](https://www.w3.org/TR/xpath-functions/#func-current-dateTime) #[inline] pub fn now() -> Result { 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 { + DateTime::now()?.try_into() + } + /// [fn:hour-from-time](https://www.w3.org/TR/xpath-functions/#func-hour-from-time) #[inline] 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 { + DateTime::now()?.try_into() + } + /// [fn:year-from-date](https://www.w3.org/TR/xpath-functions/#func-year-from-date) #[inline] pub fn year(&self) -> i64 { diff --git a/lib/oxsdatatypes/src/decimal.rs b/lib/oxsdatatypes/src/decimal.rs index c7fa6ba9..1b6fa6cc 100644 --- a/lib/oxsdatatypes/src/decimal.rs +++ b/lib/oxsdatatypes/src/decimal.rs @@ -5,10 +5,9 @@ use std::fmt::Write; use std::ops::Neg; 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_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) /// @@ -22,10 +21,9 @@ pub struct Decimal { impl Decimal { /// Constructs the decimal i / 10^n - #[allow(clippy::cast_possible_truncation)] #[inline] pub fn new(i: i128, n: u32) -> Result { - let shift = (DECIMAL_PART_DIGITS as u32) + let shift = DECIMAL_PART_DIGITS .checked_sub(n) .ok_or(DecimalOverflowError)?; Ok(Self { @@ -66,29 +64,69 @@ impl Decimal { /// [op:numeric-multiply](https://www.w3.org/TR/xpath-functions/#func-numeric-multiply) #[inline] pub fn checked_mul(&self, rhs: impl Into) -> Option { - //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 { - value: self - .value - .checked_div(DECIMAL_PART_HALF_POW)? - .checked_mul(rhs.into().value.checked_div(DECIMAL_PART_HALF_POW)?)?, + value: left + .checked_mul(right)? + .checked_mul(10_i128.checked_pow(shift)?)?, }) } /// [op:numeric-divide](https://www.w3.org/TR/xpath-functions/#func-numeric-divide) #[inline] pub fn checked_div(&self, rhs: impl Into) -> Option { - //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 { - value: self - .value - .checked_mul(DECIMAL_PART_HALF_POW)? - .checked_div(rhs.into().value)? - .checked_mul(DECIMAL_PART_HALF_POW)?, + value: left + .checked_div(right)? + .checked_div(10_i128.checked_pow(shift)?)?, }) } - /// TODO: XSD? is well defined for not integer + /// [op:numeric-mod](https://www.w3.org/TR/xpath-functions/#func-numeric-mod) #[inline] pub fn checked_rem(&self, rhs: impl Into) -> Option { Some(Self { @@ -174,9 +212,7 @@ impl Decimal { pub const MAX: Self = Self { value: i128::MAX }; #[cfg(test)] - pub(super) const fn step() -> Self { - Self { value: 1 } - } + pub const STEP: Self = Self { value: 1 }; } impl From for Decimal { @@ -316,13 +352,10 @@ impl TryFrom for Decimal { #[inline] #[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)] fn try_from(value: Double) -> Result { - let shifted = value * Double::from(DECIMAL_PART_POW as f64); - if shifted.is_finite() - && Double::from(i128::MIN as f64) <= shifted - && shifted <= Double::from(i128::MAX as f64) - { + let shifted = f64::from(value) * (DECIMAL_PART_POW as f64); + if shifted.is_finite() && (i128::MIN as f64) <= shifted && shifted <= (i128::MAX as f64) { Ok(Self { - value: f64::from(shifted) as i128, + value: shifted as i128, }) } else { Err(DecimalOverflowError) @@ -334,7 +367,7 @@ impl From for Float { #[inline] #[allow(clippy::cast_precision_loss)] 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 for Double { #[inline] #[allow(clippy::cast_precision_loss)] 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() { - Some(b'+') => (1, &input[1..]), - Some(b'-') => (-1, &input[1..]), + Some(b'+') => (1_i128, &input[1..]), + Some(b'-') => (-1_i128, &input[1..]), _ => (1, input), }; @@ -386,7 +430,7 @@ impl FromStr for Decimal { value = value .checked_mul(10) .ok_or(PARSE_OVERFLOW)? - .checked_add((*c - b'0').into()) + .checked_add(sign * i128::from(*c - b'0')) .ok_or(PARSE_OVERFLOW)?; input = &input[1..]; } else { @@ -414,7 +458,7 @@ impl FromStr for Decimal { value = value .checked_mul(10) .ok_or(PARSE_OVERFLOW)? - .checked_add((*c - b'0').into()) + .checked_add(sign * i128::from(*c - b'0')) .ok_or(PARSE_OVERFLOW)?; input = &input[1..]; } else { @@ -431,11 +475,7 @@ impl FromStr for Decimal { } Ok(Self { - value: value - .checked_mul(sign) - .ok_or(PARSE_OVERFLOW)? - .checked_mul(exp) - .ok_or(PARSE_OVERFLOW)?, + value: value.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) }) .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() { if self.value.is_negative() { width -= 1; } - if last_non_zero - DECIMAL_PART_DIGITS + 1 < width { - DECIMAL_PART_DIGITS + width + 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() { + 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 { + 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 + 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() { + for c in digits[start..decimal_part_digits].iter().rev() { 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(&Decimal::MAX.to_string())?, Decimal::MAX); - assert_eq!( - Decimal::from_str( - &Decimal::MIN - .checked_add(Decimal::step()) - .unwrap() - .to_string() - )?, - Decimal::MIN.checked_add(Decimal::step()).unwrap() - ); + assert_eq!(Decimal::from_str(&Decimal::MIN.to_string())?, Decimal::MIN); assert!(Decimal::from_str("0.0000000000000000001").is_err()); assert!(Decimal::from_str("1000000000000000000000").is_err()); assert_eq!( @@ -663,58 +696,98 @@ mod tests { #[test] fn add() { - assert!(Decimal::MIN.checked_add(Decimal::step()).is_some()); - assert!(Decimal::MAX.checked_add(Decimal::step()).is_none()); - assert_eq!( - Decimal::MAX.checked_add(Decimal::MIN), - Some(-Decimal::step()) - ); + assert!(Decimal::MIN.checked_add(Decimal::STEP).is_some()); + assert!(Decimal::MAX.checked_add(Decimal::STEP).is_none()); + assert_eq!(Decimal::MAX.checked_add(Decimal::MIN), Some(-Decimal::STEP)); } #[test] fn sub() { - assert!(Decimal::MIN.checked_sub(Decimal::step()).is_none()); - assert!(Decimal::MAX.checked_sub(Decimal::step()).is_some()); + assert!(Decimal::MIN.checked_sub(Decimal::STEP).is_none()); + assert!(Decimal::MAX.checked_sub(Decimal::STEP).is_some()); } #[test] fn mul() -> Result<(), ParseDecimalError> { + assert_eq!(Decimal::from(1).checked_mul(-1), Some(Decimal::from(-1))); assert_eq!( - Decimal::from_str("1")?.checked_mul(Decimal::from_str("-1")?), - Some(Decimal::from_str("-1")?) - ); - assert_eq!( - Decimal::from_str("1000")?.checked_mul(Decimal::from_str("1000")?), - Some(Decimal::from_str("1000000")?) + Decimal::from(1000).checked_mul(1000), + Some(Decimal::from(1000000)) ); assert_eq!( Decimal::from_str("0.1")?.checked_mul(Decimal::from_str("0.01")?), 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(()) } #[test] 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!( - Decimal::from_str("1")?.checked_div(Decimal::from_str("1")?), - Some(Decimal::from_str("1")?) + Decimal::from(10).checked_div(100), + 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!( - Decimal::from_str("100")?.checked_div(Decimal::from_str("10")?), - Some(Decimal::from_str("10")?) + Decimal::MAX.checked_div(-1), + Some(Decimal::MIN.checked_add(Decimal::STEP).unwrap()) ); + assert_eq!(Decimal::MIN.checked_div(-1), None); assert_eq!( - Decimal::from_str("10")?.checked_div(Decimal::from_str("100")?), - Some(Decimal::from_str("0.1")?) + Decimal::MIN + .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(()) } #[test] fn round() -> Result<(), ParseDecimalError> { - assert_eq!(Decimal::from_str("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(-10).round(), Decimal::from(-10)); 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.5")?.round(), Decimal::from(-2)); @@ -725,8 +798,8 @@ mod tests { #[test] fn ceil() -> Result<(), ParseDecimalError> { - assert_eq!(Decimal::from_str("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(-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(-10)); assert_eq!(Decimal::from(i64::MIN).ceil(), Decimal::from(i64::MIN)); @@ -736,8 +809,8 @@ mod tests { #[test] fn floor() -> Result<(), ParseDecimalError> { - assert_eq!(Decimal::from_str("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(-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(-11)); assert_eq!(Decimal::from(i64::MIN).floor(), Decimal::from(i64::MIN)); @@ -780,11 +853,11 @@ mod tests { fn from_float() -> Result<(), ParseDecimalError> { assert_eq!( Decimal::try_from(Float::from(0.)).ok(), - Some(Decimal::from_str("0")?) + Some(Decimal::from(0)) ); assert_eq!( Decimal::try_from(Float::from(-0.)).ok(), - Some(Decimal::from_str("0.")?) + Some(Decimal::from(0)) ); assert_eq!( Decimal::try_from(Float::from(-123.5)).ok(), @@ -798,10 +871,10 @@ mod tests { assert!( Decimal::try_from(Float::from(1_672_507_302_466.)) .unwrap() - .checked_sub(Decimal::from_str("1672507302466")?) + .checked_sub(Decimal::from(1_672_507_293_696_i64)) .unwrap() .abs() - < Decimal::from(1_000_000) + < Decimal::from(1) ); Ok(()) } @@ -810,11 +883,11 @@ mod tests { fn from_double() -> Result<(), ParseDecimalError> { assert_eq!( Decimal::try_from(Double::from(0.)).ok(), - Some(Decimal::from_str("0")?) + Some(Decimal::from(0)) ); assert_eq!( Decimal::try_from(Double::from(-0.)).ok(), - Some(Decimal::from_str("0")?) + Some(Decimal::from(0)) ); assert_eq!( Decimal::try_from(Double::from(-123.1)).ok(), @@ -823,7 +896,7 @@ mod tests { assert!( Decimal::try_from(Double::from(1_672_507_302_466.)) .unwrap() - .checked_sub(Decimal::from_str("1672507302466")?) + .checked_sub(Decimal::from(1_672_507_302_466_i64)) .unwrap() .abs() < Decimal::from(1) @@ -836,6 +909,34 @@ mod tests { 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] fn minimally_conformant() -> Result<(), ParseDecimalError> { // All minimally conforming processors must support decimal values whose absolute value can be expressed as i / 10^k, diff --git a/lib/oxsdatatypes/src/double.rs b/lib/oxsdatatypes/src/double.rs index de3b78c4..e9b26ba5 100644 --- a/lib/oxsdatatypes/src/double.rs +++ b/lib/oxsdatatypes/src/double.rs @@ -53,6 +53,12 @@ impl Double { self.value.round().into() } + #[inline] + pub fn is_nan(self) -> bool { + self.value.is_nan() + } + + #[deprecated(note = "Use .is_nan()")] #[inline] pub fn is_naan(self) -> bool { self.value.is_nan() @@ -68,6 +74,20 @@ impl Double { pub fn is_identical_with(&self, other: &Self) -> bool { 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 for f64 { @@ -243,7 +263,7 @@ mod tests { #[test] fn eq() { 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.)); } @@ -254,18 +274,15 @@ mod tests { Some(Ordering::Equal) ); assert_eq!( - Double::from(f64::INFINITY).partial_cmp(&Double::from(f64::MAX)), + Double::INFINITY.partial_cmp(&Double::MAX), Some(Ordering::Greater) ); assert_eq!( - Double::from(f64::NEG_INFINITY).partial_cmp(&Double::from(f64::MIN)), + Double::NEG_INFINITY.partial_cmp(&Double::MIN), Some(Ordering::Less) ); - assert_eq!(Double::from(f64::NAN).partial_cmp(&Double::from(0.)), None); - assert_eq!( - Double::from(f64::NAN).partial_cmp(&Double::from(f64::NAN)), - None - ); + assert_eq!(Double::NAN.partial_cmp(&Double::from(0.)), None); + assert_eq!(Double::NAN.partial_cmp(&Double::NAN), None); assert_eq!( Double::from(0.).partial_cmp(&Double::from(-0.)), Some(Ordering::Equal) @@ -275,7 +292,7 @@ mod tests { #[test] fn is_identical_with() { 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.))); } @@ -297,11 +314,11 @@ mod tests { assert_eq!(Double::from_str("-1.")?.to_string(), "-1"); assert_eq!( Double::from_str(&f64::MIN.to_string()).unwrap(), - Double::from(f64::MIN) + Double::MIN ); assert_eq!( Double::from_str(&f64::MAX.to_string()).unwrap(), - Double::from(f64::MAX) + Double::MAX ); Ok(()) } diff --git a/lib/oxsdatatypes/src/duration.rs b/lib/oxsdatatypes/src/duration.rs index 18d42912..0bc9d2bf 100644 --- a/lib/oxsdatatypes/src/duration.rs +++ b/lib/oxsdatatypes/src/duration.rs @@ -601,7 +601,7 @@ mod tests { fn from_str() -> Result<(), XsdParseError> { let min = Duration::new( 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); diff --git a/lib/oxsdatatypes/src/float.rs b/lib/oxsdatatypes/src/float.rs index 001b5006..29ebde30 100644 --- a/lib/oxsdatatypes/src/float.rs +++ b/lib/oxsdatatypes/src/float.rs @@ -53,11 +53,17 @@ impl Float { self.value.round().into() } + #[deprecated(note = "Use .is_nan()")] #[inline] pub fn is_naan(self) -> bool { self.value.is_nan() } + #[inline] + pub fn is_nan(self) -> bool { + self.value.is_nan() + } + #[inline] pub fn is_finite(self) -> bool { self.value.is_finite() @@ -68,6 +74,20 @@ impl Float { pub fn is_identical_with(&self, other: &Self) -> bool { 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 for f32 { @@ -233,7 +253,7 @@ mod tests { #[test] fn eq() { 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.)); } @@ -244,18 +264,15 @@ mod tests { Some(Ordering::Equal) ); assert_eq!( - Float::from(f32::INFINITY).partial_cmp(&Float::from(f32::MAX)), + Float::INFINITY.partial_cmp(&Float::MAX), Some(Ordering::Greater) ); assert_eq!( - Float::from(f32::NEG_INFINITY).partial_cmp(&Float::from(f32::MIN)), + Float::NEG_INFINITY.partial_cmp(&Float::MIN), Some(Ordering::Less) ); - assert_eq!(Float::from(f32::NAN).partial_cmp(&Float::from(0.)), None); - assert_eq!( - Float::from(f32::NAN).partial_cmp(&Float::from(f32::NAN)), - None - ); + assert_eq!(Float::NAN.partial_cmp(&Float::from(0.)), None); + assert_eq!(Float::NAN.partial_cmp(&Float::NAN), None); assert_eq!( Float::from(0.).partial_cmp(&Float::from(-0.)), Some(Ordering::Equal) @@ -265,7 +282,7 @@ mod tests { #[test] fn is_identical_with() { 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.))); } @@ -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(&f32::MIN.to_string())?, - Float::from(f32::MIN) - ); - assert_eq!( - Float::from_str(&f32::MAX.to_string())?, - Float::from(f32::MAX) - ); + assert_eq!(Float::from_str(&f32::MIN.to_string())?, Float::MIN); + assert_eq!(Float::from_str(&f32::MAX.to_string())?, Float::MAX); Ok(()) } } diff --git a/lib/oxsdatatypes/src/integer.rs b/lib/oxsdatatypes/src/integer.rs index 016096b3..46175fc3 100644 --- a/lib/oxsdatatypes/src/integer.rs +++ b/lib/oxsdatatypes/src/integer.rs @@ -58,6 +58,7 @@ impl Integer { }) } + /// [op:numeric-mod](https://www.w3.org/TR/xpath-functions/#func-numeric-mod) #[inline] pub fn checked_rem(&self, rhs: impl Into) -> Option { Some(Self { @@ -95,6 +96,10 @@ impl Integer { pub fn is_identical_with(&self, other: &Self) -> bool { self == other } + + pub const MIN: Self = Self { value: i64::MIN }; + + pub const MAX: Self = Self { value: i64::MAX }; } impl From for Integer { @@ -312,4 +317,40 @@ mod tests { assert!(Integer::try_from(Decimal::MAX).is_err()); 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); + } } diff --git a/lib/src/sparql/eval.rs b/lib/src/sparql/eval.rs index e95d2092..4d2d738f 100644 --- a/lib/src/sparql/eval.rs +++ b/lib/src/sparql/eval.rs @@ -930,7 +930,6 @@ impl SimpleEvaluator { } } - #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)] fn expression_evaluator( &self, expression: &PlanExpression, @@ -1426,7 +1425,11 @@ impl SimpleEvaluator { let arg = self.expression_evaluator(arg, stat_children); let dataset = Rc::clone(&self.dataset); 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) => { @@ -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 { match a { 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( dataset: &DatasetView, a: &EncodedTerm, @@ -2913,7 +2910,6 @@ enum NumericBinaryOperands { } impl NumericBinaryOperands { - #[allow(clippy::cast_precision_loss)] fn new(a: EncodedTerm, b: EncodedTerm) -> Option { match (a, b) { (EncodedTerm::FloatLiteral(v1), EncodedTerm::FloatLiteral(v2)) => {