From f920e7d4f07dc8cf5f47b21044d9b1d876090b60 Mon Sep 17 00:00:00 2001 From: Carlo Francisco Date: Tue, 21 Jan 2020 12:33:22 -0500 Subject: [PATCH 1/2] getClosestPoint: Handle floating-point arithmetic when props are decimals --- src/utils.js | 43 ++++++++++++++++++++++++++++++------------- tests/utils.test.js | 12 ++++++++++++ 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/utils.js b/src/utils.js index 5c3557865..aaa563a01 100644 --- a/src/utils.js +++ b/src/utils.js @@ -19,28 +19,45 @@ export function isNotTouchEvent(e) { (e.type.toLowerCase() === 'touchend' && e.touches.length > 0); } +export function getPrecision(step) { + const stepString = step.toString(); + let precision = 0; + if (stepString.indexOf('.') >= 0) { + precision = stepString.length - stepString.indexOf('.') - 1; + } + return precision; +} + +function withPrecision(value, precision) { + return parseFloat(value.toFixed(precision)); +} + +// safeDivideBy and safeMultiply: if either term is a float, +// then round the result to the combined precision + +function safeDivideBy(a, b) { + const precision = getPrecision(a) + getPrecision(b); + return precision === 0 ? a / b : withPrecision(a / b, precision); +} + +function safeMultiply(a, b) { + const precision = getPrecision(a) + getPrecision(b); + return precision === 0 ? a * b : withPrecision(a * b, precision); +} + export function getClosestPoint(val, { marks, step, min, max }) { const points = Object.keys(marks).map(parseFloat); if (step !== null) { - const maxSteps = Math.floor((max - min) / step); - const steps = Math.min((val - min) / step, maxSteps); + const maxSteps = Math.floor(safeDivideBy(max - min, step)); + const steps = Math.min(safeDivideBy(val - min, step), maxSteps); const closestStep = - Math.round(steps) * step + min; + safeMultiply(Math.round(steps), step) + min; points.push(closestStep); } const diffs = points.map(point => Math.abs(val - point)); return points[diffs.indexOf(Math.min(...diffs))]; } -export function getPrecision(step) { - const stepString = step.toString(); - let precision = 0; - if (stepString.indexOf('.') >= 0) { - precision = stepString.length - stepString.indexOf('.') - 1; - } - return precision; -} - export function getMousePosition(vertical, e) { return vertical ? e.clientY : e.pageX; } @@ -70,7 +87,7 @@ export function ensureValuePrecision(val, props) { const { step } = props; const closestPoint = isFinite(getClosestPoint(val, props)) ? getClosestPoint(val, props) : 0; // eslint-disable-line return step === null ? closestPoint : - parseFloat(closestPoint.toFixed(getPrecision(step))); + withPrecision(closestPoint, getPrecision(step)); } export function pauseEvent(e) { diff --git a/tests/utils.test.js b/tests/utils.test.js index 4e8381f01..843833041 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -38,5 +38,17 @@ describe('utils', () => { expect(utils.getClosestPoint(value, props)).toBe(96); }); + + it('should properly handle floating point arithmetic', () => { + const value = 5.3; + const props = { + marks: {}, + step: 0.05, + min: 0, + max: 5.3 + }; + + expect(utils.getClosestPoint(value, props)).toBe(5.3); + }); }); }); From e4768ef4f07342dac67df66d73f421d26af0a90f Mon Sep 17 00:00:00 2001 From: Carlo Francisco Date: Tue, 21 Jan 2020 13:48:37 -0500 Subject: [PATCH 2/2] getClosestPoint: enforce a precision max for the division and multiplication operations --- src/utils.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/utils.js b/src/utils.js index aaa563a01..54ae1c6dd 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,6 +1,8 @@ import { findDOMNode } from 'react-dom'; import keyCode from 'rc-util/lib/KeyCode'; +const MAX_PRECISION_FOR_OPERATIONS = 15; + export function isEventFromHandle(e, handles) { try { return Object.keys(handles) @@ -36,13 +38,19 @@ function withPrecision(value, precision) { // then round the result to the combined precision function safeDivideBy(a, b) { - const precision = getPrecision(a) + getPrecision(b); + const precision = Math.min( + getPrecision(a) + getPrecision(b), + MAX_PRECISION_FOR_OPERATIONS + ); return precision === 0 ? a / b : withPrecision(a / b, precision); } function safeMultiply(a, b) { - const precision = getPrecision(a) + getPrecision(b); - return precision === 0 ? a * b : withPrecision(a * b, precision); + const precision = Math.min( + getPrecision(a) + getPrecision(b), + MAX_PRECISION_FOR_OPERATIONS + ); + return withPrecision(a * b, precision); } export function getClosestPoint(val, { marks, step, min, max }) {