From e3aa397f41edd1f86ccc518731e10ca97a0cd6c6 Mon Sep 17 00:00:00 2001
From: dhmemi <dhmemi@gmail.com>
Date: Mon, 20 Nov 2023 20:48:18 +0800
Subject: [PATCH] fix: validate multipleOf fails on float-point value (#295)

* fix: validate multipleOf fails on float-point value

* make clang-tidy happy.

* fix test case error when multipleOf is float but number is int

* fix multiple of float number
---
 .gitignore             |  1 +
 src/json-validator.cpp |  5 +++++
 test/CMakeLists.txt    |  4 ++++
 test/issue-293.cpp     | 32 ++++++++++++++++++++++++++++++++
 4 files changed, 42 insertions(+)
 create mode 100644 test/issue-293.cpp

diff --git a/.gitignore b/.gitignore
index aceaf81..bc9dd9f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ cmake-build-*
 venv
 env
 compile_commands.json
+.vs/*
diff --git a/src/json-validator.cpp b/src/json-validator.cpp
index 6ede7c8..1fd0de1 100644
--- a/src/json-validator.cpp
+++ b/src/json-validator.cpp
@@ -864,7 +864,12 @@ class numeric : public schema
 	bool violates_multiple_of(T x) const
 	{
 		double res = std::remainder(x, multipleOf_.second);
+		double multiple = std::fabs(x / multipleOf_.second);
+		if (multiple > 1) {
+			res = res / multiple;
+		}
 		double eps = std::nextafter(x, 0) - static_cast<double>(x);
+
 		return std::fabs(res) > std::fabs(eps);
 	}
 
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 0345b34..b461df6 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -43,6 +43,10 @@ add_executable(issue-98 issue-98.cpp)
 target_link_libraries(issue-98 nlohmann_json_schema_validator)
 add_test(NAME issue-98-erase-exception-unknown-keywords COMMAND issue-98)
 
+add_executable(issue-293 issue-293.cpp)
+target_link_libraries(issue-293 nlohmann_json_schema_validator)
+add_test(NAME issue-293-float-point-error COMMAND issue-293)
+
 # Unit test for string format checks
 add_executable(string-format-check-test string-format-check-test.cpp)
 target_include_directories(string-format-check-test PRIVATE ${PROJECT_SOURCE_DIR}/src/)
diff --git a/test/issue-293.cpp b/test/issue-293.cpp
new file mode 100644
index 0000000..dbfdac9
--- /dev/null
+++ b/test/issue-293.cpp
@@ -0,0 +1,32 @@
+#include "nlohmann/json-schema.hpp"
+
+using nlohmann::json_schema::json_validator;
+
+template <typename T>
+int should_throw(const nlohmann::json &schema, T value)
+{
+	try {
+		json_validator(schema).validate(value);
+	} catch (const std::exception &ex) {
+		return 0;
+	}
+	return 1;
+}
+
+int main(void)
+{
+
+	json_validator({{"type", "number"}, {"multipleOf", 0.001}}).validate(0.3 - 0.2);
+	json_validator({{"type", "number"}, {"multipleOf", 3.3}}).validate(8.0 - 1.4);
+	json_validator({{"type", "number"}, {"multipleOf", 1000.01}}).validate((1000.03 - 0.02) * 15.0);
+	json_validator({{"type", "number"}, {"multipleOf", 0.001}}).validate(0.030999999999999993);
+	json_validator({{"type", "number"}, {"multipleOf", 0.100000}}).validate(1.9);
+	json_validator({{"type", "number"}, {"multipleOf", 100000.1}}).validate(9000009);
+
+	int exc_count = 0;
+	exc_count += should_throw({{"type", "number"}, {"multipleOf", 0.001}}, 0.3 - 0.2005);
+	exc_count += should_throw({{"type", "number"}, {"multipleOf", 1000.02}}, (1000.03 - 0.02) * 15.0);
+	exc_count += should_throw({{"type", "number"}, {"multipleOf", 100000.11}}, 9000009);
+
+	return exc_count;
+}