From 78156b1b7bdee5d52e8f0733998f09126e11487a Mon Sep 17 00:00:00 2001 From: Georgios Kalpakas Date: Tue, 13 Dec 2016 23:56:46 +0200 Subject: [PATCH 1/2] fix(input): fix `step` validation for `input[type=number/range]` Previously, the validation would incorrectly fail in certain cases (e.g. `step: 0.01`, `value: 1.16 or 20.1`), due to Floating Point Arithmetic limitations. The previous fix for FPA limitations (081d06ff) tried to solve the issue by converting the numbers to integers, before doing the actual calculation, but it failed to account for cases where the conversion itself returned non-integer values (again due to FPA limitations). This commit fixes it by ensuring that the values used in the final calculation are always integers. Fixes #15504 --- src/ng/directive/input.js | 16 ++++++++++++++-- test/ng/directive/inputSpec.js | 14 ++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index 32dd98dd842e..49f38ebbeca5 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -1565,15 +1565,27 @@ function isValidForStep(viewValue, stepBase, step) { // and `viewValue` is expected to be a valid stringified number. var value = Number(viewValue); + var isNonIntegerValue = !isNumberInteger(value); + var isNonIntegerStepBase = !isNumberInteger(stepBase); + var isNonIntegerStep = !isNumberInteger(step); + // Due to limitations in Floating Point Arithmetic (e.g. `0.3 - 0.2 !== 0.1` or // `0.5 % 0.1 !== 0`), we need to convert all numbers to integers. - if (!isNumberInteger(value) || !isNumberInteger(stepBase) || !isNumberInteger(step)) { - var decimalCount = Math.max(countDecimals(value), countDecimals(stepBase), countDecimals(step)); + if (isNonIntegerValue || isNonIntegerStepBase || isNonIntegerStep) { + var valueDecimals = isNonIntegerValue ? countDecimals(value) : 0; + var stepBaseDecimals = isNonIntegerStepBase ? countDecimals(stepBase) : 0; + var stepDecimals = isNonIntegerStep ? countDecimals(step) : 0; + + var decimalCount = Math.max(valueDecimals, stepBaseDecimals, stepDecimals); var multiplier = Math.pow(10, decimalCount); value = value * multiplier; stepBase = stepBase * multiplier; step = step * multiplier; + + if (isNonIntegerValue) value = Math.round(value); + if (isNonIntegerStepBase) stepBase = Math.round(stepBase); + if (isNonIntegerStep) step = Math.round(step); } return (value - stepBase) % step === 0; diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js index f810ce6bb78c..3dbac1a3cda8 100644 --- a/test/ng/directive/inputSpec.js +++ b/test/ng/directive/inputSpec.js @@ -2787,6 +2787,13 @@ describe('input', function() { helper.changeInputValueTo('3.5'); expect(inputElm).toBeValid(); expect($rootScope.value).toBe(3.5); + + // 1.16 % 0.01 === 0.009999999999999896 + // 1.16 * 100 === 115.99999999999999 + $rootScope.step = 0.01; + helper.changeInputValueTo('1.16'); + expect(inputElm).toBeValid(); + expect($rootScope.value).toBe(1.16); } ); }); @@ -3681,6 +3688,13 @@ describe('input', function() { helper.changeInputValueTo('3.5'); expect(inputElm).toBeValid(); expect($rootScope.value).toBe(3.5); + + // 1.16 % 0.01 === 0.009999999999999896 + // 1.16 * 100 === 115.99999999999999 + $rootScope.step = 0.01; + helper.changeInputValueTo('1.16'); + expect(inputElm).toBeValid(); + expect($rootScope.value).toBe(1.16); } ); } From c166ad75065c5097a15eb1c3714d42360b799e79 Mon Sep 17 00:00:00 2001 From: Georgios Kalpakas Date: Fri, 16 Dec 2016 00:07:03 +0200 Subject: [PATCH 2/2] fixup! fix(input): fix `step` validation for `input[type=number/range]` --- test/ng/directive/inputSpec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js index 3dbac1a3cda8..47515471122e 100644 --- a/test/ng/directive/inputSpec.js +++ b/test/ng/directive/inputSpec.js @@ -3663,7 +3663,9 @@ describe('input', function() { it('should correctly validate even in cases where the JS floating point arithmetic fails', function() { - var inputElm = helper.compileInput(''); + $rootScope.step = 0.1; + var inputElm = helper.compileInput( + ''); var ngModel = inputElm.controller('ngModel'); expect(inputElm.val()).toBe('');