Formatting: Make SI number formats more robust (#50117)

Closes #49372
This commit is contained in:
kay delaney
2022-06-07 10:48:17 +01:00
committed by GitHub
parent 3df7ee81f3
commit a9a276a9da
3 changed files with 26 additions and 62 deletions

View File

@@ -13,7 +13,7 @@ describe('currency', () => {
${1000000} | ${'M'} | ${'1'} ${1000000} | ${'M'} | ${'1'}
${1000000000} | ${'B'} | ${'1'} ${1000000000} | ${'B'} | ${'1'}
${1000000000000} | ${'T'} | ${'1'} ${1000000000000} | ${'T'} | ${'1'}
${1000000000000000} | ${undefined} | ${'NA'} ${1000000000000000} | ${'T'} | ${'1000'}
${-1000000000000} | ${'T'} | ${'-1'} ${-1000000000000} | ${'T'} | ${'-1'}
${-1000000000} | ${'B'} | ${'-1'} ${-1000000000} | ${'B'} | ${'-1'}
${-1000000} | ${'M'} | ${'-1'} ${-1000000} | ${'M'} | ${'-1'}
@@ -38,7 +38,7 @@ describe('currency', () => {
${1000000} | ${'M@'} | ${'1'} ${1000000} | ${'M@'} | ${'1'}
${1000000000} | ${'B@'} | ${'1'} ${1000000000} | ${'B@'} | ${'1'}
${1000000000000} | ${'T@'} | ${'1'} ${1000000000000} | ${'T@'} | ${'1'}
${1000000000000000} | ${undefined} | ${'NA'} ${1000000000000000} | ${'T@'} | ${'1000'}
${-1000000000000} | ${'T@'} | ${'-1'} ${-1000000000000} | ${'T@'} | ${'-1'}
${-1000000000} | ${'B@'} | ${'-1'} ${-1000000000} | ${'B@'} | ${'-1'}
${-1000000} | ${'M@'} | ${'-1'} ${-1000000} | ${'M@'} | ${'-1'}

View File

@@ -19,54 +19,22 @@ export function currency(symbol: string, asSuffix?: boolean): ValueFormatter {
}; };
} }
const SI_PREFIXES = ['f', 'p', 'n', 'µ', 'm', '', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
const SI_BASE_INDEX = SI_PREFIXES.indexOf('');
export function getOffsetFromSIPrefix(c: string): number { export function getOffsetFromSIPrefix(c: string): number {
switch (c) { const charIndex = SI_PREFIXES.findIndex((prefix) => prefix.normalize('NFKD') === c.normalize('NFKD'));
case 'f': return charIndex < 0 ? 0 : charIndex - SI_BASE_INDEX;
return -5;
case 'p':
return -4;
case 'n':
return -3;
case 'μ': // Two different unicode chars for µ
case 'µ':
return -2;
case 'm':
return -1;
case '':
return 0;
case 'k':
return 1;
case 'M':
return 2;
case 'G':
return 3;
case 'T':
return 4;
case 'P':
return 5;
case 'E':
return 6;
case 'Z':
return 7;
case 'Y':
return 8;
}
return 0;
} }
const BIN_PREFIXES = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'];
export function binaryPrefix(unit: string, offset = 0): ValueFormatter { export function binaryPrefix(unit: string, offset = 0): ValueFormatter {
const prefixes = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'].slice(offset); const units = BIN_PREFIXES.map((p) => ' ' + p + unit);
const units = prefixes.map((p) => { return scaledUnits(1024, units, offset);
return ' ' + p + unit;
});
return scaledUnits(1024, units);
} }
export function SIPrefix(unit: string, offset = 0): ValueFormatter { export function SIPrefix(unit: string, offset = 0): ValueFormatter {
let prefixes = ['f', 'p', 'n', 'µ', 'm', '', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; const units = SI_PREFIXES.map((p) => ' ' + p + unit);
prefixes = prefixes.slice(5 + (offset || 0)); return scaledUnits(1000, units, SI_BASE_INDEX + offset);
const units = prefixes.map((p) => {
return ' ' + p + unit;
});
return scaledUnits(1000, units);
} }

View File

@@ -1,3 +1,5 @@
import { clamp } from 'lodash';
import { TimeZone } from '../types'; import { TimeZone } from '../types';
import { DecimalCount } from '../types/displayValue'; import { DecimalCount } from '../types/displayValue';
@@ -129,31 +131,25 @@ export function booleanValueFormatter(t: string, f: string): ValueFormatter {
}; };
} }
// Formatter which scales the unit string geometrically according to the given const logb = (b: number, x: number) => Math.log10(x) / Math.log10(b);
// numeric factor. Repeatedly scales the value down by the factor until it is
// less than the factor in magnitude, or the end of the array is reached. export function scaledUnits(factor: number, extArray: string[], offset = 0): ValueFormatter {
export function scaledUnits(factor: number, extArray: string[]): ValueFormatter { return (size: number, decimals?: DecimalCount) => {
return (size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount) => {
if (size === null) { if (size === null) {
return { text: '' }; return { text: '' };
} }
if (size === Number.NEGATIVE_INFINITY || size === Number.POSITIVE_INFINITY || isNaN(size)) { if (size === Number.NEGATIVE_INFINITY || size === Number.POSITIVE_INFINITY || isNaN(size)) {
return { text: size.toLocaleString() }; return { text: size.toLocaleString() };
} }
let steps = 0; const siIndex = Math.floor(logb(factor, Math.abs(size)));
const limit = extArray.length; const suffix = extArray[clamp(offset + siIndex, 0, extArray.length - 1)];
while (Math.abs(size) >= factor) { return {
steps++; text: toFixed(size / factor ** clamp(siIndex, -offset, extArray.length - offset - 1), decimals),
size /= factor; suffix,
};
if (steps >= limit) {
return { text: 'NA' };
}
}
return { text: toFixed(size, decimals), suffix: extArray[steps] };
}; };
} }