Repository navigation
Fix multipleOf with integer-valued float for large integers - #1459
worksbyfriday wants to merge 1 commit into
Conversation
When multipleOf is a float with an integer value (e.g. 11.0), convert it to int before performing the check. This avoids floating-point precision loss for instances beyond 2**53, where float division produces incorrect results. Per JSON Schema spec, numbers with zero fractional parts are integers. Fixes python-jsonschema#1159
by-ae
left a comment
There was a problem hiding this comment.
I tested this merged onto current main (c497561). The merge is clean, and the full suite passes (7827 passed, 703 skipped, including the 57 multipleOf cases). The fix does what #1159 asks:
from jsonschema import Draft202012Validator as V
list(V({"multipleOf": 11.0}).iter_errors(9007199254740995))
# main: ['9007199254740995 is not a multiple of 11.0'] (false negative)
# PR: []There are also two side benefits for integer-valued float divisors, which might be worth a test each: V({"multipleOf": 11.0}).iter_errors(10**400) used to raise OverflowError: int too large to convert to float (the instance / dB is outside the try) and now validates normally, and Decimal("22") with multipleOf: 11.0 no longer raises TypeError.
One behaviour change to consider: rebinding dB also changes the error message, because line 197 formats {dB}:
list(V({"multipleOf": 11.0}).iter_errors(9007199254740996))
# main: '... is not a multiple of 11.0'
# PR: '... is not a multiple of 11'
list(V({"multipleOf": 1e308}).iter_errors(0.5))
# main: '0.5 is not a multiple of 1e+308'
# PR: '0.5 is not a multiple of 1000000000000000010979063629440455417404923096773118463368106829031575854049114915371633289784946888990612496697211725156115902837431400883283070091981460460312716645029331...' (309 digits)Using a separate local for the arithmetic (for example divisor = int(dB) if isinstance(dB, float) and dB.is_integer() else dB) and keeping dB for the message would avoid that. The schema author wrote 11.0, so echoing 11.0 back seems right.
Minor: the new test sits in TestValidationErrorMessages but doesn't check a message. If you keep it there, asserting the failing-case message too (for example 9007199254740996) would also lock in whichever message format you choose. Also, #1515 rewrites the same isinstance(dB, float) block, so whichever lands second will need a small rebase.
Summary
When
multipleOfis an integer-valued float (e.g.11.0), the validator uses float division, which loses precision for instances beyond2**53. This causes false negatives:9007199254740995is a multiple of11, but9007199254740995 / 11.0loses precision and the check fails.The fix converts integer-valued floats to
intbefore performing the divisibility check, usingfloat.is_integer(). This ensures thatmultipleOf: 11.0behaves identically tomultipleOf: 11, per the JSON Schema spec which states that numbers with zero fractional parts are integers.Non-integer floats (e.g.
0.5) are unaffected — theisinstance(dB, float)branch still handles them.Fixes #1159.