vega-frontend-monorepo/libs/utils/src/lib/format/number.ts

257 lines
7.4 KiB
TypeScript

import { BigNumber } from 'bignumber.js';
import isNil from 'lodash/isNil';
import memoize from 'lodash/memoize';
import { getUserLocale } from '../get-user-locale';
/**
* A raw unformatted value greater than this is considered and displayed
* as UNLIMITED.
*/
export const UNLIMITED_THRESHOLD = new BigNumber(2).pow(256).times(0.8);
/**
* Gets the unlimited threshold value for given decimal places.
* @param decimalPlaces the asset's decimal places
*/
export const getUnlimitedThreshold = (decimalPlaces: number) =>
UNLIMITED_THRESHOLD.dividedBy(Math.pow(10, decimalPlaces));
const MIN_FRACTION_DIGITS = 2;
const MAX_FRACTION_DIGITS = 20;
export function toDecimal(numberOfDecimals: number) {
return new BigNumber(1)
.dividedBy(new BigNumber(10).exponentiatedBy(numberOfDecimals))
.toString(10);
}
export function toBigNum(
rawValue: string | number | BigNumber,
decimals: number
): BigNumber {
const divides = new BigNumber(10).exponentiatedBy(decimals);
return new BigNumber(rawValue || 0).dividedBy(divides);
}
export function addDecimal(
value: string | number,
decimals: number,
decimalPrecision = decimals
): string {
if (!decimals) return value.toString();
if (!decimalPrecision || decimalPrecision < 0) {
return toBigNum(value, decimals).toFixed(0);
}
return toBigNum(value, decimals).toFixed(decimalPrecision);
}
export function removeDecimal(
value: string | BigNumber,
decimals: number
): string {
const times = new BigNumber(10).exponentiatedBy(decimals);
return new BigNumber(value || 0).times(times).toFixed(0);
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat
export const getNumberFormat = memoize((digits: number) => {
if (isNil(digits) || digits < 0) {
return new Intl.NumberFormat(getUserLocale());
}
return new Intl.NumberFormat(getUserLocale(), {
minimumFractionDigits: Math.min(Math.max(0, digits), MIN_FRACTION_DIGITS),
maximumFractionDigits: Math.min(Math.max(0, digits), MAX_FRACTION_DIGITS),
});
});
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat
export const getFixedNumberFormat = memoize((digits: number) => {
if (isNil(digits) || digits < 0) {
return new Intl.NumberFormat(getUserLocale());
}
return new Intl.NumberFormat(getUserLocale(), {
minimumFractionDigits: Math.min(Math.max(0, digits), MAX_FRACTION_DIGITS),
maximumFractionDigits: Math.min(Math.max(0, digits), MAX_FRACTION_DIGITS),
});
});
export const getDecimalSeparator = memoize(
() =>
getNumberFormat(1)
.formatToParts(1.1)
.find((part) => part.type === 'decimal')?.value
);
/** formatNumber will format the number with fixed decimals
* @param rawValue - should be a number that is not outside the safe range fail as in https://mikemcl.github.io/bignumber.js/#toN
* @param formatDecimals - number of decimals to use
*/
export const formatNumber = (
rawValue: string | number | BigNumber,
formatDecimals = 0
) => {
return getNumberFormat(formatDecimals).format(Number(rawValue));
};
/** formatNumberFixed will format the number with fixed decimals
* @param rawValue - should be a number that is not outside the safe range fail as in https://mikemcl.github.io/bignumber.js/#toN
* @param formatDecimals - number of decimals to use
*/
export const formatNumberFixed = (
rawValue: string | number | BigNumber,
formatDecimals = 0
) => {
return getFixedNumberFormat(formatDecimals).format(Number(rawValue));
};
export const quantumDecimalPlaces = (
/** Raw asset's quantum value */
rawQuantum: number | string,
/** Asset's decimal places */
decimalPlaces: number
) => {
// if raw quantum value is an empty string then it'll evaluate to 0
// this check ignores NaNs and zeroes
const formatDecimals =
isNaN(Number(rawQuantum)) || Number(rawQuantum) === 0
? decimalPlaces
: Math.max(
0,
Math.log10(100 / Number(addDecimal(rawQuantum, decimalPlaces)))
);
return Math.ceil(formatDecimals);
};
export const addDecimalsFormatNumberQuantum = (
rawValue: string | number,
decimalPlaces: number,
quantum: number | string
) => {
if (isNaN(Number(quantum))) {
return addDecimalsFormatNumber(rawValue, decimalPlaces);
}
const quantumValue = addDecimal(quantum, decimalPlaces);
const numberDP = Math.max(0, Math.log10(100 / Number(quantumValue)));
return addDecimalsFormatNumber(rawValue, decimalPlaces, Math.ceil(numberDP));
};
export const addDecimalsFormatNumber = (
rawValue: string | number,
decimalPlaces: number,
formatDecimals: number = decimalPlaces
) => {
const x = addDecimal(rawValue, decimalPlaces);
return formatNumber(x, formatDecimals);
};
export const addDecimalsFixedFormatNumber = (
rawValue: string | number,
decimalPlaces: number,
formatDecimals: number = decimalPlaces
) => {
const x = addDecimal(rawValue, decimalPlaces);
return formatNumberFixed(x, formatDecimals);
};
export const formatNumberPercentage = (
value: BigNumber | null | undefined,
decimals?: number
) => {
if (!value) {
return '-';
}
const decimalPlaces =
typeof decimals === 'undefined' ? value.dp() || 0 : decimals;
return `${formatNumber(value, decimalPlaces)}%`;
};
export const toNumberParts = (
value: BigNumber | null | undefined,
decimals = 18
): [integers: string, decimalPlaces: string, separator: string] => {
if (!value) {
return ['0', '0'.repeat(decimals), '.'];
}
const separator = getDecimalSeparator() || '.';
const [integers, decimalsPlaces] = formatNumber(value, decimals)
.toString()
.split(separator);
return [integers, decimalsPlaces || '', separator];
};
export const isNumeric = (
value?: string | number | BigNumber | bigint | null
): value is NonNullable<number | string> => /^-?\d*\.?\d+$/.test(String(value));
/**
* Format a number greater than 1 million with m for million, b for billion
* and t for trillion
*/
export const formatNumberRounded = (
num: BigNumber,
limit: '1e12' | '1e9' | '1e6' | '1e3' = '1e6'
) => {
let value = '';
const format = (divisor: string) => {
const result = num.dividedBy(divisor);
return result.isInteger() ? result.toString() : result.toFixed(1);
};
if (num.isGreaterThan(new BigNumber('1e14'))) {
value = '>100t';
} else if (
num.isGreaterThanOrEqualTo(limit) &&
num.isGreaterThanOrEqualTo(new BigNumber('1e12'))
) {
// Trillion
value = `${format('1e12')}t`;
} else if (
num.isGreaterThanOrEqualTo(limit) &&
num.isGreaterThanOrEqualTo(new BigNumber('1e9'))
) {
// Billion
value = `${format('1e9')}b`;
} else if (
num.isGreaterThanOrEqualTo(limit) &&
num.isGreaterThanOrEqualTo(new BigNumber('1e6'))
) {
// Million
value = `${format('1e6')}m`;
} else if (
num.isGreaterThanOrEqualTo(limit) &&
num.isGreaterThanOrEqualTo(new BigNumber('1e3'))
) {
value = `${format('1e3')}k`;
} else {
value = formatNumber(num);
}
return value;
};
/**
* Converts given amount in one asset (determined by raw amount
* and quantum values) to qUSD.
* @param amount The raw amount
* @param quantum The quantum value of the asset.
*/
export const toQUSD = (
amount: string | number | BigNumber,
quantum: string | number
) => {
const value = new BigNumber(amount);
let q = new BigNumber(quantum);
if (q.isNaN() || q.isLessThanOrEqualTo(0)) {
q = new BigNumber(1);
}
const qUSD = value.dividedBy(q);
return qUSD;
};