Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(number-field): fix Floating point Division on validate method #757

Merged
merged 6 commits into from
Jun 19, 2023
Merged
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fixture, expect } from '@refinitiv-ui/test-helpers';
import { fixture, expect, elementUpdated } from '@refinitiv-ui/test-helpers';

import '@refinitiv-ui/elements/number-field';
import '@refinitiv-ui/elemental-theme/light/ef-number-field';
Expand All @@ -24,3 +24,21 @@ describe('number-field/Validity', () => {
});
});
});

describe('Check Floating point', function () {
// Test Floating point precision issue that results approximation of real number. e.g. 1111111/0.00001 should equal to 111111100000.
describe('Input remains valid upon value update with a step of float value', function () {
it('step = 0.00001 and value = 1111111', async function () {
const el = await fixture('<ef-number-field step="0.00001"></ef-number-field>');
el.value="1111111";
await elementUpdated(el);
expect(el.checkValidity()).to.be.equal(true);
});
it('step = 0.14 and value = 7', async function () {
const el = await fixture('<ef-number-field step="0.14"></ef-number-field>');
el.value="7";
await elementUpdated(el);
expect(el.checkValidity()).to.be.equal(true);
});
});
});
30 changes: 29 additions & 1 deletion packages/elements/src/number-field/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,29 @@ export class NumberField extends FormFieldElement {
return 0;
}

/**
* Count precision number
* @param number value to count
* @returns precision number
*/
private getPrecision (number: number): number {
const getDecimalPrecision = (number: string | number): number => {
Theeraphat-Sorasetsakul marked this conversation as resolved.
Show resolved Hide resolved
const [wholeNumber, decimalNumber] = number.toString().split('.');
Theeraphat-Sorasetsakul marked this conversation as resolved.
Show resolved Hide resolved
return (wholeNumber.length ?? 0) + (decimalNumber?.length ?? 0);
};

const numberString = number.toString();

// Check if the number is in exponential notation.
if (numberString.includes('e')) {
const [mantissa, exponent] = numberString.split('e');
const precision = getDecimalPrecision(mantissa) + Math.abs(Number(exponent));
return precision;
}

return getDecimalPrecision(numberString);
}

/**
* Check if value subtracted from the step base is not an integral multiple of the allowed value step
* @param value value to check
Expand All @@ -542,7 +565,12 @@ export class NumberField extends FormFieldElement {
return true;
}
const decimals = Math.max(this.getDecimalPlace(value), this.stepDecimals);
const division = (this.stepBase - value) / this.getAllowedValueStep();
const dividend = this.stepBase - value;
const divisor = this.getAllowedValueStep();
// calculate precision to prevent Floating point precision issue.
// e.g. 1111111/0.00001 would not result in 111111100000 as expected.
const precision = this.getPrecision(dividend) + this.getPrecision(divisor);
const division = parseFloat((dividend / divisor).toPrecision(precision));
const number = decimals ? this.toFixedNumber(division, decimals) : division;

// (2 - 1.01) % 0.33 needs to give 0. So we cannot use % directly as it is intended for integers
Expand Down