Skip to content

Fix multipleOf with integer-valued float for large integers - #1459

Open
worksbyfriday wants to merge 1 commit into
python-jsonschema:mainfrom
worksbyfriday:fix-multipleOf-integer-float
Open

worksbyfriday wants to merge 1 commit into
python-jsonschema:mainfrom
worksbyfriday:fix-multipleOf-integer-float

Conversation

@worksbyfriday

Copy link
Copy Markdown

Summary

When multipleOf is an integer-valued float (e.g. 11.0), the validator uses float division, which loses precision for instances beyond 2**53. This causes false negatives: 9007199254740995 is a multiple of 11, but 9007199254740995 / 11.0 loses precision and the check fails.

The fix converts integer-valued floats to int before performing the divisibility check, using float.is_integer(). This ensures that multipleOf: 11.0 behaves identically to multipleOf: 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 — the isinstance(dB, float) branch still handles them.

Fixes #1159.

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
@Julian Julian added the Needs Test Upstream Issues that need to have a test added to https://github.com/json-schema-org/JSON-Schema-Test-Suite label Aug 1, 2026

@by-ae by-ae left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

This branch was previously deployed

1 inactive deployment
PyPI — 08a8c225 Deployed Mar 1, 2026 by worksbyfriday via packaging #4130
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs Test Upstream Issues that need to have a test added to https://github.com/json-schema-org/JSON-Schema-Test-Suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Instance validates if multipleOf is an integer, but fails if it's a float

3 participants