diff --git a/lib/jsonschema.lua b/lib/jsonschema.lua index 1f69b18..54e82e3 100644 --- a/lib/jsonschema.lua +++ b/lib/jsonschema.lua @@ -984,9 +984,14 @@ generate_validator = function(ctx, schema) -- integer multipleOf: modulo is enough ctx:stmt(sformat(' if %s %% %d ~= 0 then', ctx:param(1), mof)) else - -- float multipleOf: it's a bit more hacky and slow + -- float multipleOf: use relative tolerance to handle IEEE 754 + -- precision errors. e.g. 1.13 / 0.01 = 112.99999999999999 + -- We check whether the fractional part of the quotient is + -- negligible relative to its magnitude. ctx:stmt(sformat(' local quotient = %s / %s', ctx:param(1), mof)) - ctx:stmt(sformat(' if %s(quotient) ~= quotient then', ctx:libfunc('math.modf'))) + ctx:stmt(sformat(' local rounded = %s(quotient + 0.5)', ctx:libfunc('math.floor'))) + ctx:stmt( ' local tol = 1e-12 * (rounded == 0 and 1 or (rounded < 0 and -rounded or rounded))') + ctx:stmt(sformat(' if %s(quotient - rounded) > tol then', ctx:libfunc('math.abs'))) end ctx:stmt(sformat( ' return false, %s("expected %%s to be a multiple of %s", %s)', ctx:libfunc('string.format'), mof, ctx:param(1))) diff --git a/spec/extra/multipleOf.json b/spec/extra/multipleOf.json new file mode 100644 index 0000000..22c6880 --- /dev/null +++ b/spec/extra/multipleOf.json @@ -0,0 +1,74 @@ +[ + { + "description": "multipleOf with float precision", + "schema": { + "type": "number", + "multipleOf": 0.01 + }, + "tests": [ + { + "description": "1.13 is a multiple of 0.01", + "data": 1.13, + "valid": true + }, + { + "description": "0.01 is a multiple of 0.01", + "data": 0.01, + "valid": true + }, + { + "description": "100.05 is a multiple of 0.01", + "data": 100.05, + "valid": true + }, + { + "description": "1.1312 is not a multiple of 0.01", + "data": 1.1312, + "valid": false + }, + { + "description": "0.015 is not a multiple of 0.01", + "data": 0.015, + "valid": false + } + ] + }, + { + "description": "multipleOf with integer", + "schema": { + "type": "number", + "multipleOf": 3 + }, + "tests": [ + { + "description": "9 is a multiple of 3", + "data": 9, + "valid": true + }, + { + "description": "10 is not a multiple of 3", + "data": 10, + "valid": false + } + ] + }, + { + "description": "multipleOf with large values", + "schema": { + "type": "number", + "multipleOf": 1000000000.5 + }, + "tests": [ + { + "description": "exact large multiple is valid", + "data": 2000000001.0, + "valid": true + }, + { + "description": "large value offset by 0.05 is invalid", + "data": 1000000000.55, + "valid": false + } + ] + } +] diff --git a/t/draft4.lua b/t/draft4.lua index 90004f8..3b392cd 100644 --- a/t/draft4.lua +++ b/t/draft4.lua @@ -47,6 +47,7 @@ local supported = { "spec/extra/dependencies.json", "spec/extra/table.json", "spec/extra/ref.json", + "spec/extra/multipleOf.json", 'spec/JSON-Schema-Test-Suite/tests/draft4/type.json', 'spec/JSON-Schema-Test-Suite/tests/draft4/default.json', diff --git a/t/draft7.lua b/t/draft7.lua index 8677c23..8425773 100644 --- a/t/draft7.lua +++ b/t/draft7.lua @@ -50,6 +50,7 @@ local supported = { "spec/extra/ref.json", "spec/extra/format.json", "spec/extra/default.json", + "spec/extra/multipleOf.json", 'spec/JSON-Schema-Test-Suite/tests/draft7/type.json', 'spec/JSON-Schema-Test-Suite/tests/draft7/default.json',