Skip to content

Commit 743ff3d

Browse files
author
Sascha Goldhofer
committed
fix: #43 floating point issue in multipleOf validation
- fix multipleOf validation logic - remove floatingPointPrecision setting
1 parent d6a307c commit 743ff3d

File tree

12 files changed

+151
-16
lines changed

12 files changed

+151
-16
lines changed

dist/jsonSchemaLibrary.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/lib/config/settings.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
declare const _default: {
22
DECLARATOR_ONEOF: string;
33
GET_TEMPLATE_RECURSION_LIMIT: number;
4-
floatingPointPrecision: number;
54
propertyBlacklist: string[];
65
templateDefaultOptions: {
76
addOptionalProps: boolean;

dist/lib/utils/getPrecision.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/**
2+
* returns the floating point precision of a decimal number or 0
3+
*/
4+
export declare function getPrecision(value: number): number;

dist/module/lib/config/settings.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
export default {
22
DECLARATOR_ONEOF: "oneOfProperty",
33
GET_TEMPLATE_RECURSION_LIMIT: 1,
4-
floatingPointPrecision: 10000,
54
propertyBlacklist: ["_id"],
65
templateDefaultOptions: {
76
addOptionalProps: false,

dist/module/lib/utils/getPrecision.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* returns the floating point precision of a decimal number or 0
3+
*/
4+
export function getPrecision(value) {
5+
const string = `${value}`;
6+
const index = string.indexOf(".");
7+
return index === -1 ? 0 : string.length - (index + 1);
8+
}

dist/module/lib/validation/keyword.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { validateAllOf } from "../features/allOf";
77
import { validateAnyOf } from "../features/anyOf";
88
import { validateDependencies } from "../features/dependencies";
99
import { validateOneOf } from "../features/oneOf";
10+
import { getPrecision } from "../utils/getPrecision";
1011
import deepEqual from "fast-deep-equal";
11-
const FPP = settings.floatingPointPrecision;
1212
const hasOwnProperty = Object.prototype.hasOwnProperty;
1313
const hasProperty = (value, property) => !(value[property] === undefined || !hasOwnProperty.call(value, property));
1414
// list of validation keywords: http://json-schema.org/latest/json-schema-validation.html#rfc.section.5
@@ -283,12 +283,24 @@ const KeywordValidation = {
283283
return undefined;
284284
},
285285
multipleOf: (draft, schema, value, pointer) => {
286-
if (isNaN(schema.multipleOf)) {
286+
if (isNaN(schema.multipleOf) || typeof value !== "number") {
287287
return undefined;
288288
}
289-
// https://github.com/cfworker/cfworker/blob/master/packages/json-schema/src/validate.ts#L1061
290-
// https://github.com/ExodusMovement/schemasafe/blob/master/src/compile.js#L441
291-
if (((value * FPP) % (schema.multipleOf * FPP)) / FPP !== 0) {
289+
const valuePrecision = getPrecision(value);
290+
const multiplePrecision = getPrecision(schema.multipleOf);
291+
if (valuePrecision > multiplePrecision) {
292+
// higher precision of value can never be multiple of value
293+
return draft.errors.multipleOfError({
294+
multipleOf: schema.multipleOf,
295+
value,
296+
pointer,
297+
schema
298+
});
299+
}
300+
const precision = Math.pow(10, multiplePrecision);
301+
const val = Math.round(value * precision);
302+
const multiple = Math.round(schema.multipleOf * precision);
303+
if ((val % multiple) / precision !== 0) {
292304
return draft.errors.multipleOfError({
293305
multipleOf: schema.multipleOf,
294306
value,

lib/config/settings.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
export default {
22
DECLARATOR_ONEOF: "oneOfProperty",
33
GET_TEMPLATE_RECURSION_LIMIT: 1,
4-
floatingPointPrecision: 10000,
54
propertyBlacklist: ["_id"],
65
templateDefaultOptions: {
76
addOptionalProps: false,

lib/getChildSchemaSelection.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ export default function getChildSchemaSelection(
2424
}
2525

2626
const result = draft.step(property, schema, {}, "#");
27-
2827
if (isJsonError(result)) {
2928
return result;
3029
}

lib/utils/getPrecision.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* returns the floating point precision of a decimal number or 0
3+
*/
4+
export function getPrecision(value: number): number {
5+
const string = `${value}`;
6+
const index = string.indexOf(".");
7+
return index === -1 ? 0 : string.length - (index + 1);
8+
}

lib/validation/keyword.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { validateAllOf } from "../features/allOf";
77
import { validateAnyOf } from "../features/anyOf";
88
import { validateDependencies } from "../features/dependencies";
99
import { validateOneOf } from "../features/oneOf";
10+
import { getPrecision } from "../utils/getPrecision";
1011
import deepEqual from "fast-deep-equal";
11-
const FPP = settings.floatingPointPrecision;
1212

1313
const hasOwnProperty = Object.prototype.hasOwnProperty;
1414
const hasProperty = (value: Record<string, unknown>, property: string) =>
@@ -316,20 +316,36 @@ const KeywordValidation: Record<string, JsonValidator> = {
316316
return undefined;
317317
},
318318
multipleOf: (draft, schema, value: number, pointer) => {
319-
if (isNaN(schema.multipleOf)) {
319+
if (isNaN(schema.multipleOf) || typeof value !== "number") {
320320
return undefined;
321321
}
322-
// https://github.com/cfworker/cfworker/blob/master/packages/json-schema/src/validate.ts#L1061
323-
// https://github.com/ExodusMovement/schemasafe/blob/master/src/compile.js#L441
324-
if (((value * FPP) % (schema.multipleOf * FPP)) / FPP !== 0) {
322+
323+
const valuePrecision = getPrecision(value);
324+
const multiplePrecision = getPrecision(schema.multipleOf);
325+
if (valuePrecision > multiplePrecision) {
326+
// value with higher precision then multipleOf-precision can never be multiple
327+
return draft.errors.multipleOfError({
328+
multipleOf: schema.multipleOf,
329+
value,
330+
pointer,
331+
schema
332+
});
333+
}
334+
335+
const precision = Math.pow(10, multiplePrecision);
336+
const val = Math.round(value * precision);
337+
const multiple = Math.round(schema.multipleOf * precision);
338+
if ((val % multiple) / precision !== 0) {
325339
return draft.errors.multipleOfError({
326340
multipleOf: schema.multipleOf,
327341
value,
328342
pointer,
329343
schema
330344
});
331345
}
332-
// also check https://stackoverflow.com/questions/1815367/catch-and-compute-overflow-during-multiplication-of-two-large-integers
346+
347+
// maybe also check overflow
348+
// https://stackoverflow.com/questions/1815367/catch-and-compute-overflow-during-multiplication-of-two-large-integers
333349
return undefined;
334350
},
335351
not: (draft, schema, value, pointer) => {

0 commit comments

Comments
 (0)