Skip to content

Commit

Permalink
fix(input): fix step validation for input[type=number/range]
Browse files Browse the repository at this point in the history
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 (081d06f) 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 angular#15504

Closes angular#15506
  • Loading branch information
gkalpak authored and ellimist committed Mar 15, 2017
1 parent 1cf0181 commit df6d7c9
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 3 deletions.
16 changes: 14 additions & 2 deletions src/ng/directive/input.js
Expand Up @@ -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;
Expand Down
18 changes: 17 additions & 1 deletion test/ng/directive/inputSpec.js
Expand Up @@ -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);
}
);
});
Expand Down Expand Up @@ -3656,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('<input type="range" ng-model="value" step="0.1" />');
$rootScope.step = 0.1;
var inputElm = helper.compileInput(
'<input type="range" ng-model="value" step="{{step}}" />');
var ngModel = inputElm.controller('ngModel');

expect(inputElm.val()).toBe('');
Expand All @@ -3681,6 +3690,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);
}
);
}
Expand Down

0 comments on commit df6d7c9

Please sign in to comment.