Skip to content

Instantly share code, notes, and snippets.

@alamilladev
Last active September 13, 2023 02:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alamilladev/52c873f1d956afbc533e3e58256fd314 to your computer and use it in GitHub Desktop.
Save alamilladev/52c873f1d956afbc533e3e58256fd314 to your computer and use it in GitHub Desktop.
Typescript function to format numbers with currency, thousand separators, fixed and significant decimals.
export const defaultCurrency = {
id: 'USD',
symbol: '$',
full: 'USD$',
};
export const isNegative = (num: number): boolean => num < 0;
/**
* ===========================================
* |******** CURRENCY FORMAT METHODS ********|
* ===========================================
*/
/**
* Converts a number in scientific notation to its non-scientific representation.
* @param numString - The number in scientific notation as a string.
* @returns The number in non-scientific notation as a string.
*/
function convertToNonScientific(num: string | number): string {
if (typeof num === 'number') {
num = String(num);
}
// Check if the number string is in scientific notation
if (num.includes('e')) {
// Convert to non-exponential form
const arrNum: string[] = num.split('e-');
const strDecimal: string = arrNum[0].replace('.', '');
const numDecimalsToSubtract: number = arrNum[0].split('.')[0].length;
const numZeros = Number(arrNum[1]) - numDecimalsToSubtract;
return '0.' + '0'.repeat(numZeros) + strDecimal;
}
return String(num);
}
/**
* Formats a number with the specified decimal places, ensuring the significant digits are retained.
* @param number - The number to format.
* @param decimalPlaces - The number of decimal places to keep after rounding.
* @returns The formatted number as a string with the specified decimal places.
*/
function formatWithSignificantDecimals(
number: number,
decimalPlaces: number
): string {
// Convert the number to a string
let strNum: string = String(number);
strNum = convertToNonScientific(strNum);
// Find the decimal part of the number
const numParts: string[] = strNum.split('.');
const decimalPart: string | undefined = numParts[1];
// Find the number of leading zeros in the decimal part
let leadingZeros: number = 0;
if (decimalPart) {
while (decimalPart[leadingZeros] === '0') {
leadingZeros++;
}
}
// Determine the total digits after the decimal point considering leading zeros
const totalDigits: number = leadingZeros + decimalPlaces;
// Round the number
const roundedNumber: number =
Math.round(parseFloat(strNum) * Math.pow(10, totalDigits)) /
Math.pow(10, totalDigits);
return convertToNonScientific(roundedNumber);
}
export type FormatToCurrencyInput = {
num?: string | number | null;
currency?: string | null;
decimalPlaces?: number | null;
significantDecimalPlaces?: number | null;
};
/**
* Formats a number as a currency string with the specified decimal places and currency symbol.
* @returns The formatted currency string.
*/
export function formatToCurrency(input: FormatToCurrencyInput): string {
let num: number | string = input.num || 0;
let currency: string | null | undefined = input.currency;
const decimalPlaces: number = input.decimalPlaces || 2;
const significantDecimalPlaces: number = input.significantDecimalPlaces || 8;
if (!currency && currency !== '') currency = defaultCurrency.symbol;
if (typeof num === 'string') num = Number(num);
const absNum: number = Math.abs(num);
const negativeSimbol: string = isNegative(num) ? '-' : '';
// Format numbers with thousand separator and fixed decimals (2 default decimals)
if (absNum > 1) {
num = negativeSimbol + absNum.toFixed(decimalPlaces);
return (
currency +
Number(num).toLocaleString('en-US', {
minimumFractionDigits: decimalPlaces,
})
);
}
// Format numbers with significan decimals (8 default decimals)
const numWithSigDecimals: string = formatWithSignificantDecimals(
absNum,
significantDecimalPlaces
);
const numParts: string[] = numWithSigDecimals.split('.');
const decimalPart: string | undefined = numParts[1];
if (!decimalPart || decimalPart.length < 2) {
return currency + negativeSimbol + Number(numWithSigDecimals).toFixed(2);
}
return currency + negativeSimbol + numWithSigDecimals;
}
/**
* Extracts a raw number from a string, removing any non-digit and non-decimal point characters.
*
* @param strNum - The string containing the number.
* @returns The raw number extracted from the string.
*/
export function getRawNumber(strNum: string): number {
if (strNum === null || strNum === undefined) return 0;
// Remove any characters from the string that are not digits or decimal points.
// This regex [^\d.] matches any character that is not a digit or a decimal point.
const rawNumber: string = strNum.replace(/[^\d.]/g, '');
const floatNumber: number = parseFloat(rawNumber);
return isNaN(floatNumber) ? 0 : floatNumber;
}
/**
* Rounds a number up to the specified number of decimal places.
*
* @param num - The number to round.
* @param decimalPlaces - The number of decimal places to keep after rounding.
* @returns The rounded number up to the specified decimal places.
*/
export function roundUp(num: number, decimalPlaces: number): number {
// Multiply the number by 10 raised to the power of the decimal places.
// This moves the decimal point to the right, converting the decimals into integer digits.
const scaledNumber = num * Math.pow(10, decimalPlaces);
// Use Math.ceil to round up to the nearest integer.
const roundedNumber = Math.ceil(scaledNumber);
// Finally, divide the rounded number by 10 raised to the power of the decimal places
// to restore the decimal point to its original position and get the rounded result.
return roundedNumber / Math.pow(10, decimalPlaces);
}
export function roundDown(num: number, decimalPlaces: number): number {
// Multiply the number by 10 raised to the power of the decimal places.
// This moves the decimal point to the right, converting the decimals into integer digits.
const scaledNumber = num * Math.pow(10, decimalPlaces);
// Use Math.floor to round down to the nearest integer.
const roundedNumber = Math.floor(scaledNumber);
// Finally, divide the rounded number by 10 raised to the power of the decimal places
// to restore the decimal point to its original position and get the rounded result.
return roundedNumber / Math.pow(10, decimalPlaces);
}
export function formatPercentageChange(value: number | string | null) {
return `(${formatToCurrency({ num: value, currency: '' })} %)`;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment