diff --git a/jsonschema/_keywords.py b/jsonschema/_keywords.py index f30f9541..1fde97f3 100644 --- a/jsonschema/_keywords.py +++ b/jsonschema/_keywords.py @@ -168,6 +168,12 @@ def multipleOf(validator, dB, instance, schema): if not validator.is_type(instance, "number"): return + # JSON Schema: numbers with zero fractional parts are integers. + # Convert integer-valued floats (e.g. 11.0) to int to avoid + # floating-point precision loss with large instances. + if isinstance(dB, float) and dB.is_integer(): + dB = int(dB) + if isinstance(dB, float): quotient = instance / dB try: diff --git a/jsonschema/tests/test_validators.py b/jsonschema/tests/test_validators.py index 7d8a4c5c..8021413c 100644 --- a/jsonschema/tests/test_validators.py +++ b/jsonschema/tests/test_validators.py @@ -501,6 +501,23 @@ def test_multipleOf(self): ) self.assertEqual(message, "3 is not a multiple of 2") + def test_multipleOf_integer_valued_float(self): + """Integer-valued floats like 11.0 should behave like integers (#1159). + + Large integers beyond 2**53 lose precision in float division, + so multipleOf with an integer-valued float must use integer + arithmetic to avoid false negatives. + """ + instance = 9007199254740995 # 2**53 + 3, a multiple of 11 + # With integer divisor this passes + validators.Draft202012Validator( + {"type": "integer", "multipleOf": 11}, + ).validate(instance) + # With integer-valued float divisor this should also pass + validators.Draft202012Validator( + {"type": "integer", "multipleOf": 11.0}, + ).validate(instance) + def test_minItems(self): message = self.message_for(instance=[], schema={"minItems": 2}) self.assertEqual(message, "[] is too short")