Skip to content

Commit 96f5d6f

Browse files
authored
fix: multipleOf float precision with tolerance-based check (#98)
1 parent 7268433 commit 96f5d6f

4 files changed

Lines changed: 83 additions & 2 deletions

File tree

lib/jsonschema.lua

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -984,9 +984,14 @@ generate_validator = function(ctx, schema)
984984
-- integer multipleOf: modulo is enough
985985
ctx:stmt(sformat(' if %s %% %d ~= 0 then', ctx:param(1), mof))
986986
else
987-
-- float multipleOf: it's a bit more hacky and slow
987+
-- float multipleOf: use relative tolerance to handle IEEE 754
988+
-- precision errors. e.g. 1.13 / 0.01 = 112.99999999999999
989+
-- We check whether the fractional part of the quotient is
990+
-- negligible relative to its magnitude.
988991
ctx:stmt(sformat(' local quotient = %s / %s', ctx:param(1), mof))
989-
ctx:stmt(sformat(' if %s(quotient) ~= quotient then', ctx:libfunc('math.modf')))
992+
ctx:stmt(sformat(' local rounded = %s(quotient + 0.5)', ctx:libfunc('math.floor')))
993+
ctx:stmt( ' local tol = 1e-12 * (rounded == 0 and 1 or (rounded < 0 and -rounded or rounded))')
994+
ctx:stmt(sformat(' if %s(quotient - rounded) > tol then', ctx:libfunc('math.abs')))
990995
end
991996
ctx:stmt(sformat( ' return false, %s("expected %%s to be a multiple of %s", %s)',
992997
ctx:libfunc('string.format'), mof, ctx:param(1)))

spec/extra/multipleOf.json

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
[
2+
{
3+
"description": "multipleOf with float precision",
4+
"schema": {
5+
"type": "number",
6+
"multipleOf": 0.01
7+
},
8+
"tests": [
9+
{
10+
"description": "1.13 is a multiple of 0.01",
11+
"data": 1.13,
12+
"valid": true
13+
},
14+
{
15+
"description": "0.01 is a multiple of 0.01",
16+
"data": 0.01,
17+
"valid": true
18+
},
19+
{
20+
"description": "100.05 is a multiple of 0.01",
21+
"data": 100.05,
22+
"valid": true
23+
},
24+
{
25+
"description": "1.1312 is not a multiple of 0.01",
26+
"data": 1.1312,
27+
"valid": false
28+
},
29+
{
30+
"description": "0.015 is not a multiple of 0.01",
31+
"data": 0.015,
32+
"valid": false
33+
}
34+
]
35+
},
36+
{
37+
"description": "multipleOf with integer",
38+
"schema": {
39+
"type": "number",
40+
"multipleOf": 3
41+
},
42+
"tests": [
43+
{
44+
"description": "9 is a multiple of 3",
45+
"data": 9,
46+
"valid": true
47+
},
48+
{
49+
"description": "10 is not a multiple of 3",
50+
"data": 10,
51+
"valid": false
52+
}
53+
]
54+
},
55+
{
56+
"description": "multipleOf with large values",
57+
"schema": {
58+
"type": "number",
59+
"multipleOf": 1000000000.5
60+
},
61+
"tests": [
62+
{
63+
"description": "exact large multiple is valid",
64+
"data": 2000000001.0,
65+
"valid": true
66+
},
67+
{
68+
"description": "large value offset by 0.05 is invalid",
69+
"data": 1000000000.55,
70+
"valid": false
71+
}
72+
]
73+
}
74+
]

t/draft4.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ local supported = {
4747
"spec/extra/dependencies.json",
4848
"spec/extra/table.json",
4949
"spec/extra/ref.json",
50+
"spec/extra/multipleOf.json",
5051

5152
'spec/JSON-Schema-Test-Suite/tests/draft4/type.json',
5253
'spec/JSON-Schema-Test-Suite/tests/draft4/default.json',

t/draft7.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ local supported = {
5050
"spec/extra/ref.json",
5151
"spec/extra/format.json",
5252
"spec/extra/default.json",
53+
"spec/extra/multipleOf.json",
5354

5455
'spec/JSON-Schema-Test-Suite/tests/draft7/type.json',
5556
'spec/JSON-Schema-Test-Suite/tests/draft7/default.json',

0 commit comments

Comments
 (0)