fix(common): handle JS floating point errors in percent pipe (#20329)
Fixes #20136 PR Close #20329
This commit is contained in:
parent
5a7bf36723
commit
07b81ae741
|
@ -46,11 +46,6 @@ export function formatNumber(
|
|||
num = value;
|
||||
}
|
||||
|
||||
if (style === NumberFormatStyle.Percent) {
|
||||
num = num * 100;
|
||||
}
|
||||
|
||||
const numStr = Math.abs(num) + '';
|
||||
const pattern = parseNumberFormat(format, getLocaleNumberSymbol(locale, NumberSymbol.MinusSign));
|
||||
let formattedText = '';
|
||||
let isZero = false;
|
||||
|
@ -58,7 +53,11 @@ export function formatNumber(
|
|||
if (!isFinite(num)) {
|
||||
formattedText = getLocaleNumberSymbol(locale, NumberSymbol.Infinity);
|
||||
} else {
|
||||
const parsedNumber = parseNumber(numStr);
|
||||
let parsedNumber = parseNumber(num);
|
||||
|
||||
if (style === NumberFormatStyle.Percent) {
|
||||
parsedNumber = toPercent(parsedNumber);
|
||||
}
|
||||
|
||||
let minInt = pattern.minInt;
|
||||
let minFraction = pattern.minFrac;
|
||||
|
@ -249,11 +248,35 @@ interface ParsedNumber {
|
|||
integerLen: number;
|
||||
}
|
||||
|
||||
// Transforms a parsed number into a percentage by multiplying it by 100
|
||||
function toPercent(parsedNumber: ParsedNumber): ParsedNumber {
|
||||
// if the number is 0, don't do anything
|
||||
if (parsedNumber.digits[0] === 0) {
|
||||
return parsedNumber;
|
||||
}
|
||||
|
||||
// Getting the current number of decimals
|
||||
const fractionLen = parsedNumber.digits.length - parsedNumber.integerLen;
|
||||
if (parsedNumber.exponent) {
|
||||
parsedNumber.exponent += 2;
|
||||
} else {
|
||||
if (fractionLen === 0) {
|
||||
parsedNumber.digits.push(0, 0);
|
||||
} else if (fractionLen === 1) {
|
||||
parsedNumber.digits.push(0);
|
||||
}
|
||||
parsedNumber.integerLen += 2;
|
||||
}
|
||||
|
||||
return parsedNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a number (as a string)
|
||||
* Parses a number.
|
||||
* Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/
|
||||
*/
|
||||
function parseNumber(numStr: string): ParsedNumber {
|
||||
function parseNumber(num: number): ParsedNumber {
|
||||
let numStr = Math.abs(num) + '';
|
||||
let exponent = 0, digits, integerLen;
|
||||
let i, j, zeros;
|
||||
|
||||
|
@ -356,12 +379,23 @@ function roundNumber(parsedNumber: ParsedNumber, minFrac: number, maxFrac: numbe
|
|||
// Pad out with zeros to get the required fraction length
|
||||
for (; fractionLen < Math.max(0, fractionSize); fractionLen++) digits.push(0);
|
||||
|
||||
|
||||
let dropTrailingZeros = fractionSize !== 0;
|
||||
// Minimal length = nb of decimals required + current nb of integers
|
||||
// Any number besides that is optional and can be removed if it's a trailing 0
|
||||
const minLen = minFrac + parsedNumber.integerLen;
|
||||
// Do any carrying, e.g. a digit was rounded up to 10
|
||||
const carry = digits.reduceRight(function(carry, d, i, digits) {
|
||||
d = d + carry;
|
||||
digits[i] = d % 10;
|
||||
return Math.floor(d / 10);
|
||||
digits[i] = d < 10 ? d : d - 10; // d % 10
|
||||
if (dropTrailingZeros) {
|
||||
// Do not keep meaningless fractional trailing zeros (e.g. 15.52000 --> 15.52)
|
||||
if (digits[i] === 0 && i >= minLen) {
|
||||
digits.pop();
|
||||
} else {
|
||||
dropTrailingZeros = false;
|
||||
}
|
||||
}
|
||||
return d >= 10 ? 1 : 0; // Math.floor(d / 10);
|
||||
}, 0);
|
||||
if (carry) {
|
||||
digits.unshift(carry);
|
||||
|
|
|
@ -79,6 +79,22 @@ export function main() {
|
|||
expect(pipe.transform(1.2, '.2')).toEqual('120.00%');
|
||||
expect(pipe.transform(1.2, '4.2')).toEqual('0,120.00%');
|
||||
expect(pipe.transform(1.2, '4.2', 'fr')).toEqual('0 120,00 %');
|
||||
// see issue #20136
|
||||
expect(pipe.transform(0.12345674, '0.0-10')).toEqual('12.345674%');
|
||||
expect(pipe.transform(0, '0.0-10')).toEqual('0%');
|
||||
expect(pipe.transform(0.00, '0.0-10')).toEqual('0%');
|
||||
expect(pipe.transform(1, '0.0-10')).toEqual('100%');
|
||||
expect(pipe.transform(0.1, '0.0-10')).toEqual('10%');
|
||||
expect(pipe.transform(0.12, '0.0-10')).toEqual('12%');
|
||||
expect(pipe.transform(0.123, '0.0-10')).toEqual('12.3%');
|
||||
expect(pipe.transform(12.3456, '0.0-10')).toEqual('1,234.56%');
|
||||
expect(pipe.transform(12.345600, '0.0-10')).toEqual('1,234.56%');
|
||||
expect(pipe.transform(12.345699999, '0.0-6')).toEqual('1,234.57%');
|
||||
expect(pipe.transform(12.345699999, '0.4-6')).toEqual('1,234.5700%');
|
||||
expect(pipe.transform(100, '0.4-6')).toEqual('10,000.0000%');
|
||||
expect(pipe.transform(100, '0.0-10')).toEqual('10,000%');
|
||||
expect(pipe.transform(1.5e2)).toEqual('15,000%');
|
||||
expect(pipe.transform(1e100)).toEqual('1E+102%');
|
||||
});
|
||||
|
||||
it('should not support other objects', () => {
|
||||
|
|
Loading…
Reference in New Issue