Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions jsonschema/_keywords.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
17 changes: 17 additions & 0 deletions jsonschema/tests/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down