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

Closes #15506
This commit is contained in:
Georgios Kalpakas
2016-12-13 23:56:46 +02:00
parent 99a3adbdaa
commit a24777a2c4
2 changed files with 31 additions and 3 deletions
+14 -2
View File
@@ -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;
+17 -1
View File
@@ -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);
}
);
});
@@ -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('');
@@ -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);
}
);
}