import { StringUtils } from "./StringUtils.js"; const NUMBERINPUTS_REGEX_SCIENTIFIC_PARTS: RegExp = /^([+-])?(\d+).?(\d*)[eE]([-+]?\d+)$/; /** * `NumberUtils`: Utilizado para manipulação de numerais. */ export class NumberUtils { /** * Converte o dado numérico de string para number. * * @param value Numeral em formato de string a ser convertido (Importante: formato PT-BR ou já em formato numérico: ######.##). * @returns - Numeral passado. * @example * @"100,12" | 100.12 * @"100.12" | 100.12 * @"-100,12" | -100.12 * @"R$100,12" | 100.12 * @"-R$100,12" | -100.12 * @"string" | NaN */ static stringToNumber = (value: string): number => { if (value === '' || value === null || value === undefined) { return NaN; } if (value) { value = value.toString(); const negative = (value.charAt(0) === '-'); if (!NUMBERINPUTS_REGEX_SCIENTIFIC_PARTS.test(value)) { value = value.replace(/[^\d.,]/g, ''); } else { value = value.replace(/^-/g, ''); } //In case of simple string such as: "@@@@@@@" if (value === '') { return Number(NaN); } const indexV = value.indexOf(','); const indexP = value.indexOf('.'); if (indexP > indexV) { value = value.replace(',', ''); } else if (indexP < indexV) { value = value.replace(/\./g, '@').replace(',', '.').replace(/@/g, ''); } if (negative) { value = '-' + value; } } return Number(value); } /** * Formata o numeral com a precisão informada. * * @param value - Numeral em formato de string a ser convertido (Importante: formato PT-BR ou já em formato numérico - sem separador de milhares: ######.##). * @param precision - Quantidade de casas decimais. * @param prettyPrecision - Quantidade de zeros nos decimais. * @param defaultValue - Valor padrão caso o value não seja um valor numérico válido. * * @returns - Numeral em formato de String formatado em PT-BR. * @example * Informado: ('10,9845444', 3, 3) | Retorna: '10,985' * Informado: (undefined, 3, 3) | Retorna: NaN * Informado: (undefined, 3, 3, '0,00') | Retorna: 0,00 */ static format = (value: string, precision: number, prettyPrecision: number = NaN, defaultValue:string = NaN.toString()): string => { if (value === '' || value === undefined || value === "NaN") { return defaultValue; } const newValue: number = NumberUtils.stringToNumber(value); if (isNaN(newValue)) { return defaultValue; } //Validation "precision": // Case1: precision < 0 => does not use precision // Case2: presicion not int => does not use precision if (precision < 0 || Math.abs(Math.round(precision * 1) / 1) !== precision) { //Once stringToNumber returns number format, we need to change to pt-br return NumberUtils.changeFormat(newValue.toString()); } //Validation "prettyPrecision" let prettyPrecisionInternal: number = 0; // Case1: prettyPrecision < 0 => prettyPrecision does not change de format that means: prettyPrecisionInternal = precision; // Case2: prettyPrecision not int => prettyPrecision does not change de format that means: prettyPrecisionInternal = precision; if (isNaN(prettyPrecision) || Math.abs(Math.round(prettyPrecision * 1) / 1) !== prettyPrecision) { prettyPrecisionInternal = precision; } else { prettyPrecisionInternal = prettyPrecision; } let newValueStr: string; newValueStr = (Math.round(newValue * Math.pow(10, precision)) / Math.pow(10, precision)).toLocaleString('pt-br', { minimumFractionDigits: precision }); //prettyPrecision const varSettingPP: number = precision - prettyPrecisionInternal; if (varSettingPP > 0) { for (let i = 0; i < varSettingPP; i++) { if (newValueStr.substring(newValueStr.length - 1) == "0" && precision > 0) { newValueStr = newValueStr.substring(0, newValueStr.length - 1); } } } //in Case "." or "," in the end of the string if (newValueStr.substring(newValueStr.length - 1) == "." || newValueStr.substring(newValueStr.length - 1) == ",") { newValueStr = newValueStr.substring(0, newValueStr.length - 1); } return newValueStr; } /** * Formata o numeral com a precisão informada e o valor default 0,00 caso o value não seja um numero valido. * * @param value - Numeral em formato de string a ser convertido (Importante: formato PT-BR ou já em formato numérico - sem separador de milhares: ######.##). * @param precision - Quantidade de casas decimais. * @param prettyPrecision - Quantidade de zeros nos decimais. * * @returns - Numeral em formato de String formatado em PT-BR. * @example * Informado: ('10,9845444', 3, 3) | Retorna: '10,985' * Informado: (undefined, 3, 3) | Retorna: 0,00 */ static safeFormat = (value: string, precision: number, prettyPrecision: number = NaN): string => { return NumberUtils.format(value, precision, prettyPrecision, "0,00"); } /** * Retira os separadores de milhar de um numeral em formato de string. * * @param value - Numeral em formato de string a ser convertido. * @param formatnumber - Formatação de ENTRADA e SAÍDA do utilitário: pt-BR="###.###,##" en-US="###,###.##"; Default: "pt-BR". * * @returns - Numeral em formato de string formatado apenas com separador decimal. * @example * Informado: '95.12' | Retorna: '9512' */ static keepOnlyDecimalSeparator = (value: string, formatnumber: string = 'pt-BR'): string => { //Formatting formatnumber to be able to get lowercases formatnumber = formatnumber.toUpperCase(); //Formatting value following formatnumber parameter //keep only decimal character in order to correct format the string //This transformation is due the "stringtoNumber" method is a general method that tries to convert all formated strings if (formatnumber === 'EN-US') { value = value.replace(/,/g, ''); } else { value = value.replace(/\./g, ''); } return value; } /** * Troca o formato do numeral(string) de "PT-BR" para "EN-US" e vice-versa. * * @param value - Numeral em formato de string a ser convertido. * * @returns - Numeral em formato de string formatado de "PT-BR" para "EN-US" e vice-versa. * @example * Informo: "15,55" | Obtenho: "15.55" * @example * Informo: "27.99" | Obtenho: "27,99" */ static changeFormat = (value: string): string => { //Formatting output following formatnumber return value.replace(/\./g, '_').replace(/,/g, '.').replace(/_/g, ','); } /** * Obtém o valor ou o valor padrão, caso o valor seja inválido(NaN/undefined). * * @param value - Valor a ser validado. * @param defaultValue - Valor padrão a ser retornado caso o value seja inválido. * * @returns - O próprio numeral passado ou zero. * * @example * Informo: value: 30, defaultValue: 0 | Obtenho: 30 * @example * Informo: value: "30", defaultValue: 0 | Obtenho: 30 * @example * Informo: value: "30abc", defaultValue: 0 | Obtenho: 0 */ static getValueOrDefault = (value: any, defaultValue: number): number => { value = StringUtils.isEmpty(value) ? undefined : value; value = Number(value); if (isNaN(value)){ return Number(defaultValue); } return value; } /** * Obtém o valor ou zero, caso o valor seja inválido(NaN/undefined). * * @param value - Numeral a ser validado. * * @returns - O próprio numeral passado. Caso esse seja inválido retorna zero. */ static getValueOrZero = (value: any): number => { return NumberUtils.getValueOrDefault(value, 0); } /** * Realiza o arredondamento de casas decimais de um numero. * * @param value - Numeral a ser arredondado. * @param decimals - Quantidade de casas decimais ussada no arredondamento. * * @returns - O próprio numeral arredondado com a quantidade de casas decimais do argumento decimals. * * @example * Informo: (100.12) | 100.12 * Informo: (100,12) | NaN * Informo: ("100.12", 1) | 100.1 * Informo: ("100.12", 2) | 100.12 * Informo: ("100.12", 3) | 100.12 * Informo: ("100.15", 1) | 100.2 * Informo: ("100.15", 2) | 100.15 * Informo: ("100.15", 3) | 100.15 * Informo: ("100.16", 1) | 100.2 * Informo: ("100.16", 2) | 100.16 * Informo: ("100.16", 3) | 100.16 */ static round(value: number, decimals: number = 2):number { const multiplier = 10 ** decimals; return Math.round(value * multiplier) / multiplier; } /** * Determina a ordem de umeros. * * @param a - Primeio número para comparação. * @param b - Segundo número para comparação. * @returns - Um numeral negativo se o primeiro argumento é menor que o segundo, zero se os dois são iguais e um numeral positivo quando o primeiro é maior que o segundo. */ static compare(a: number, b: number): number{ if(a === undefined){ return b === undefined ? 0 : 1; } else if(b === undefined){ return -1; } if(a === null){ return b === null ? 0 : 1; } else if(b === null){ return -1; } return a > b ? 1 : (a < b ? -1 : 0); } }