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:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user