diff --git a/jsonschema/src/keywords/multiple_of.rs b/jsonschema/src/keywords/multiple_of.rs index 4f0b4d10..64c15091 100644 --- a/jsonschema/src/keywords/multiple_of.rs +++ b/jsonschema/src/keywords/multiple_of.rs @@ -9,87 +9,33 @@ use crate::{ use fraction::{BigFraction, BigUint}; use serde_json::{Map, Value}; -pub(crate) struct MultipleOfFloatValidator { +pub(crate) struct MultipleOfValidator { multiple_of: f64, schema_path: JSONPointer, } -impl MultipleOfFloatValidator { +impl MultipleOfValidator { #[inline] pub(crate) fn compile<'a>(multiple_of: f64, schema_path: JSONPointer) -> CompilationResult<'a> { - Ok(Box::new(MultipleOfFloatValidator { + Ok(Box::new(MultipleOfValidator { multiple_of, schema_path, })) } } -impl Validate for MultipleOfFloatValidator { +impl Validate for MultipleOfValidator { fn is_valid(&self, instance: &Value) -> bool { if let Value::Number(item) = instance { - let item = item.as_f64().expect("Always valid"); - let remainder = (item / self.multiple_of) % 1.; - if remainder.is_nan() { - // Involves heap allocations via the underlying `BigUint` type - let fraction = BigFraction::from(item) / BigFraction::from(self.multiple_of); - if let Some(denom) = fraction.denom() { - denom == &BigUint::from(1_u8) - } else { - true - } - } else { - remainder < f64::EPSILON - } - } else { - true - } - } + let mut tmp_item = item.as_f64().expect("Always valid"); + let mut tmp_multiple_of = self.multiple_of; - fn validate<'instance>( - &self, - instance: &'instance Value, - instance_path: &InstancePath, - ) -> ErrorIterator<'instance> { - if !self.is_valid(instance) { - return error(ValidationError::multiple_of( - self.schema_path.clone(), - instance_path.into(), - instance, - self.multiple_of, - )); - } - no_error() - } -} - -impl core::fmt::Display for MultipleOfFloatValidator { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "multipleOf: {}", self.multiple_of) - } -} - -pub(crate) struct MultipleOfIntegerValidator { - multiple_of: f64, - schema_path: JSONPointer, -} - -impl MultipleOfIntegerValidator { - #[inline] - pub(crate) fn compile<'a>(multiple_of: f64, schema_path: JSONPointer) -> CompilationResult<'a> { - Ok(Box::new(MultipleOfIntegerValidator { - multiple_of, - schema_path, - })) - } -} + while tmp_item.fract() != 0. { + tmp_item *= 10.0; + tmp_multiple_of *= 10.0; + } -impl Validate for MultipleOfIntegerValidator { - fn is_valid(&self, instance: &Value) -> bool { - if let Value::Number(item) = instance { - let item = item.as_f64().expect("Always valid"); - // As the divisor has its fractional part as zero, then any value with a non-zero - // fractional part can't be a multiple of this divisor, therefore it is short-circuited - item.fract() == 0. && (item % self.multiple_of) == 0. + tmp_item % tmp_multiple_of == 0.0 } else { true } @@ -112,7 +58,7 @@ impl Validate for MultipleOfIntegerValidator { } } -impl core::fmt::Display for MultipleOfIntegerValidator { +impl core::fmt::Display for MultipleOfValidator { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "multipleOf: {}", self.multiple_of) } @@ -126,14 +72,10 @@ pub(crate) fn compile<'a>( if let Value::Number(multiple_of) = schema { let multiple_of = multiple_of.as_f64().expect("Always valid"); let schema_path = context.as_pointer_with("multipleOf"); - if multiple_of.fract() == 0. { - Some(MultipleOfIntegerValidator::compile( - multiple_of, - schema_path, - )) - } else { - Some(MultipleOfFloatValidator::compile(multiple_of, schema_path)) - } + Some(MultipleOfValidator::compile( + multiple_of, + schema_path, + )) } else { Some(Err(ValidationError::single_type_error( JSONPointer::default(), @@ -154,11 +96,18 @@ mod tests { #[test_case(&json!({"multipleOf": 1.0}), &json!(4.0))] #[test_case(&json!({"multipleOf": 1.5}), &json!(3.0))] #[test_case(&json!({"multipleOf": 1.5}), &json!(4.5))] + #[test_case(&json!({"multipleOf": 0.1}), &json!(1.1))] + #[test_case(&json!({"multipleOf": 0.1}), &json!(1.2))] + #[test_case(&json!({"multipleOf": 0.1}), &json!(1.3))] + #[test_case(&json!({"multipleOf": 0.02}), &json!(1.02))] fn multiple_of_is_valid(schema: &Value, instance: &Value) { tests_util::is_valid(schema, instance) } #[test_case(&json!({"multipleOf": 1.0}), &json!(4.5))] + #[test_case(&json!({"multipleOf": 0.1}), &json!(4.55))] + #[test_case(&json!({"multipleOf": 0.2}), &json!(4.5))] + #[test_case(&json!({"multipleOf": 0.02}), &json!(1.01))] fn multiple_of_is_not_valid(schema: &Value, instance: &Value) { tests_util::is_not_valid(schema, instance) }