Skip to content

Commit

Permalink
fix(kit): InputNumber has rounding problems on blur with float larg…
Browse files Browse the repository at this point in the history
…e numbers (#9974)
  • Loading branch information
nsbarsukov authored Dec 12, 2024
1 parent b144aec commit 9ec1e7b
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"import": ["@taiga-ui/cspell-config/cspell.config.js"],
"files": ["*/*.*"],
"ignorePaths": ["**/projects/i18n/languages/**", "**/addon-commerce/utils/get-currency-symbol.ts"],
"ignoreWords": ["Wachovia", "bottomsheet", "appbar", "qwertypgj_", "antialiasing", "xxxs"],
"ignoreWords": ["Wachovia", "bottomsheet", "appbar", "qwertypgj_", "antialiasing", "xxxs", "significand"],
"ignoreRegExpList": ["\\(https?://.*?\\)", "\\/{1}.+\\/{1}", "\\%2F.+", "\\%2C.+", "\\ɵ.+", "\\ыва.+"],
"overrides": [
{
Expand Down
20 changes: 17 additions & 3 deletions projects/cdk/utils/math/round.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,19 @@ function calculate(

precision = Math.min(precision, MAX_PRECISION);

const pair = `${value}e`.split('e');
const tempValue = func(Number(`${pair[0]}e${Number(pair[1]) + precision}`));
const processedPair = `${tempValue}e`.split('e');
const [significand, exponent = ''] = `${value}`.split('e');
const roundedInt = func(Number(`${significand}e${Number(exponent) + precision}`));

/**
* TODO: use BigInt after bumping Safari to 14+
*/
ngDevMode &&
console.assert(
Number.isSafeInteger(roundedInt),
'Impossible to correctly round such a large number',
);

const processedPair = `${roundedInt}e`.split('e');

return Number(`${processedPair[0]}e${Number(processedPair[1]) - precision}`);
}
Expand All @@ -45,3 +55,7 @@ export function tuiFloor(value: number, precision = 0): number {
export function tuiTrunc(value: number, precision = 0): number {
return calculate(value, precision, Math.trunc);
}

export function tuiIsSafeToRound(value: number, precision = 0): boolean {
return Number.isSafeInteger(Math.trunc(value * 10 ** precision));
}
10 changes: 10 additions & 0 deletions projects/core/utils/format/test/format-number.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,14 @@ describe('Number formatting', () => {
}),
).toBe('0');
});

it('does not mutate value if precision is infinite', () => {
expect(
tuiFormatNumber(123_456_789_012_345.67, {
precision: Infinity,
thousandSeparator: ',',
rounding: 'round',
}),
).toBe('123,456,789,012,345.67');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,22 @@ test.describe('InputNumber', () => {
await expect(example).toHaveScreenshot('01-input-number.png');
});

test('does not mutate already valid too large number on blur', async ({page}) => {
await tuiGoto(
page,
`${DemoRoute.InputNumber}/API?thousandSeparator=_&precision=2`,
);
await input.focus();
await input.clear();
await input.pressSequentially('123456789012345.6789');

await expect(input).toHaveValue('123_456_789_012_345.67');

await input.blur();

await expect(input).toHaveValue('123_456_789_012_345.67');
});

test('prefix + value + postfix', async ({page}) => {
await tuiGoto(
page,
Expand Down
12 changes: 10 additions & 2 deletions projects/legacy/components/input-number/input-number.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import type {TuiValueTransformer} from '@taiga-ui/cdk/classes';
import {CHAR_HYPHEN, CHAR_MINUS, EMPTY_QUERY} from '@taiga-ui/cdk/constants';
import {tuiWatch} from '@taiga-ui/cdk/observables';
import {TUI_IS_IOS} from '@taiga-ui/cdk/tokens';
import {tuiClamp} from '@taiga-ui/cdk/utils/math';
import {tuiClamp, tuiIsSafeToRound} from '@taiga-ui/cdk/utils/math';
import {tuiCreateToken, tuiPure} from '@taiga-ui/cdk/utils/miscellaneous';
import type {TuiDecimalMode} from '@taiga-ui/core/tokens';
import {TUI_DEFAULT_NUMBER_FORMAT, TUI_NUMBER_FORMAT} from '@taiga-ui/core/tokens';
Expand Down Expand Up @@ -274,7 +274,15 @@ export class TuiInputNumberComponent
this.computedPrefix +
tuiFormatNumber(value, {
...this.numberFormat,
precision: this.precision,
/**
* Number can satisfy interval [Number.MIN_SAFE_INTEGER; Number.MAX_SAFE_INTEGER]
* but its rounding can violate it.
* Before BigInt support there is no perfect solution – only trade off.
* No rounding is better than lose precision and incorrect mutation of already valid value.
*/
precision: tuiIsSafeToRound(value, this.precision)
? this.precision
: Infinity,
}).replace(CHAR_HYPHEN, CHAR_MINUS) +
this.computedPostfix
);
Expand Down

0 comments on commit 9ec1e7b

Please sign in to comment.