Repository: networknt/json-schema-validator
Pull request #140 (2019-04-29 19:11:56)

Fix multipleOf validator 
Related issue: https://github.com/networknt/json-schema-validator/pull/139

Since the division and multiple of double type do not have the enough accuracy. Convert dividend and divisor into BigDecimal to do the validation would be more reliable.


Patch:
diff --git a/src/main/java/com/networknt/schema/MultipleOfValidator.java b/src/main/java/com/networknt/schema/MultipleOfValidator.java
index 75f8770..3374b50 100644
--- a/src/main/java/com/networknt/schema/MultipleOfValidator.java
+++ b/src/main/java/com/networknt/schema/MultipleOfValidator.java
@@ -1,57 +1,60 @@
-/*
- * Copyright (c) 2016 Network New Technologies Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.networknt.schema;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Collections;
-import java.util.Set;
-
-public class MultipleOfValidator extends BaseJsonValidator implements JsonValidator {
-    private static final Logger logger = LoggerFactory.getLogger(MultipleOfValidator.class);
-
-    private double divisor = 0;
-
-    public MultipleOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
-
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MULTIPLE_OF, validationContext);
-        if (schemaNode.isNumber()) {
-            divisor = schemaNode.doubleValue();
-        }
-
-        parseErrorCode(getValidatorType().getErrorCodeKey());
-    }
-
-    public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
-        debug(logger, node, rootNode, at);
-
-        if (node.isNumber()) {
-            double nodeValue = node.doubleValue();
-            if (divisor != 0) {
-                long multiples = Math.round(nodeValue / divisor);
-                if (Math.abs(multiples * divisor - nodeValue) > 1e-12) {
-                    return Collections.singleton(buildValidationMessage(at, "" + divisor));
-                }
-            }
-        }
-
-        return Collections.emptySet();
-    }
-
-}
+/*
+ * Copyright (c) 2016 Network New Technologies Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigDecimal;
+import java.util.Collections;
+import java.util.Set;
+
+public class MultipleOfValidator extends BaseJsonValidator implements JsonValidator {
+    private static final Logger logger = LoggerFactory.getLogger(MultipleOfValidator.class);
+
+    private double divisor = 0;
+
+    public MultipleOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MULTIPLE_OF, validationContext);
+        if (schemaNode.isNumber()) {
+            divisor = schemaNode.doubleValue();
+        }
+
+        parseErrorCode(getValidatorType().getErrorCodeKey());
+    }
+
+    public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
+        debug(logger, node, rootNode, at);
+
+        if (node.isNumber()) {
+            double nodeValue = node.doubleValue();
+            if (divisor != 0) {
+                // convert to BigDecimal since double type is not accurate enough to do the division and multiple
+                BigDecimal accurateDividend = new BigDecimal(String.valueOf(nodeValue));
+                BigDecimal accurateDivisor = new BigDecimal(String.valueOf(divisor));
+                if (Math.abs(accurateDividend.divideAndRemainder(accurateDivisor)[1].doubleValue()) > 1e-12) {
+                    return Collections.singleton(buildValidationMessage(at, "" + divisor));
+                }
+            }
+        }
+
+        return Collections.emptySet();
+    }
+
+}


Test patch:
diff --git a/src/test/resources/tests/multipleOf.json b/src/test/resources/tests/multipleOf.json
index ca3b761..8576e0c 100644
--- a/src/test/resources/tests/multipleOf.json
+++ b/src/test/resources/tests/multipleOf.json
@@ -56,5 +56,38 @@
                 "valid": false
             }
         ]
+    },
+    {
+        "description": "by small number",
+        "schema": {
+            "multipleOf": 0.01
+        },
+        "tests": [
+            {
+                "description": "9313.7 is a multiple of 0.01",
+                "data": 9313.7,
+                "valid": true
+            },
+            {
+                "description": "9313.9 is a multiple of 0.01",
+                "data": 9313.9,
+                "valid": true
+            },
+            {
+                "description": "9313.8 is a multiple of 0.01",
+                "data": 9313.8,
+                "valid": true
+            },
+            {
+                "description": "9313.8 is a multiple of 0.01",
+                "data": -9313.8861,
+                "valid": false
+            },
+            {
+                "description": "9313.8 is a multiple of 0.01",
+                "data": -9313.8,
+                "valid": true
+            }
+        ]
     }
 ]