UNPKG

nhb-toolbox

Version:

A versatile collection of smart, efficient, and reusable utility functions, classes and types for everyday development needs.

244 lines (243 loc) 8.17 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.romanToInteger = exports.convertToRomanNumerals = void 0; exports.numberToWords = numberToWords; exports.numberToWordsOrdinal = numberToWordsOrdinal; exports.wordsToNumber = wordsToNumber; exports.banglaToDigit = banglaToDigit; exports.digitToBangla = digitToBangla; const primitives_1 = require("../guards/primitives"); const specials_1 = require("../guards/specials"); const constants_1 = require("./constants"); const helpers_1 = require("./helpers"); const utilities_1 = require("./utilities"); function numberToWords(num) { let number = Math.trunc(Number(num)); if (!Number.isFinite(number) || Number.isNaN(number)) { return 'Invalid Number!'; } const isNegative = number < 0; if (number === 0) return 'zero'; number = Math.abs(number); let i = 0; let result = ''; while (number > 0) { if (i >= constants_1.THOUSANDS.length) { return `Number exceeds supported range (max is 10e19 aka 10^20)`; } if (number % 1000 !== 0) { const isLastGroup = i === 0 && number % 100 < 100; const prefix = (0, helpers_1._convertLessThanThousand)(number % 1000, isLastGroup); result = `${prefix} ${constants_1.THOUSANDS[i]} ${result}`; } number = Math.floor(number / 1000); i++; } const finalResult = result.trim().replace(/\s+/g, ' '); return isNegative ? `minus ${finalResult}` : finalResult; } const convertToRomanNumerals = (value) => { let num = (0, utilities_1.normalizeNumber)(value); if (!(0, primitives_1.isInteger)(num) || num <= 0 || num >= 4000) { throw new RangeError('Value must be an integer and between 1 and 3999'); } const romanMap = [ [1000, 'M'], [900, 'CM'], [500, 'D'], [400, 'CD'], [100, 'C'], [90, 'XC'], [50, 'L'], [40, 'XL'], [10, 'X'], [9, 'IX'], [5, 'V'], [4, 'IV'], [1, 'I'], ]; let result = ''; for (const [value, numeral] of romanMap) { while (num >= value) { result += numeral; num -= value; } } return result; }; exports.convertToRomanNumerals = convertToRomanNumerals; const romanToInteger = (roman) => { const romanMap = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000, }; if (!(0, primitives_1.isNonEmptyString)(roman) || !roman?.trim()) { throw new TypeError('Input must be a non-empty string'); } const upperRoman = roman?.toUpperCase()?.trim(); let total = 0; let prevValue = 0; for (let i = upperRoman.length - 1; i >= 0; i--) { const char = upperRoman[i]; const value = romanMap[char]; if (!value) { throw new Error(`Invalid Roman numeral character: '${char}'`); } if (value < prevValue) { total -= value; } else { total += value; prevValue = value; } } if (total <= 0 || total >= 4000) { throw new RangeError('Resulting number must be between 1 and 3999'); } if ((0, exports.convertToRomanNumerals)(total) !== upperRoman) { throw new Error('Invalid or malformed Roman numeral!'); } return total; }; exports.romanToInteger = romanToInteger; function numberToWordsOrdinal(number) { const TEEN_OR_HUNDRED = /(teen|hundred|thousand|(m|b|tr|quadr)illion)$/; const UNDER_TEEN = /(zero|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)$/; const _fixUnderTeen = (cardinal) => { return constants_1.ORDINAL_UNDER_TEEN[cardinal]; }; const wordNumber = (0, specials_1.isNumericString)(number) || (0, primitives_1.isNumber)(number) ? numberToWords(number) : number?.trim()?.toLowerCase(); if (TEEN_OR_HUNDRED.test(wordNumber)) { return wordNumber + 'th'; } else if (/y$/.test(wordNumber)) { return wordNumber.replace(/y$/, 'ieth'); } else if (UNDER_TEEN.test(wordNumber)) { return wordNumber.replace(UNDER_TEEN, _fixUnderTeen); } else { return wordNumber; } } function wordsToNumber(word) { if (!(0, primitives_1.isNonEmptyString)(word)) return NaN; const trimmed = word.trim(); if (/^[+-]?\d{1,3}(,\d{3})*(\.\d+)?$/.test(trimmed)) { return Number(trimmed.replace(/,/g, '')); } if (/^[+-]?\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed); let input = trimmed.toLowerCase(); let negative = false; input = input.replace(/^\s*(minus|negative)\s+/, () => { negative = true; return ''; }); input = input .replace(/-/g, ' ') .replace(/\s+and\s+/g, ' ') .replace(/\s+/g, ' ') .trim(); if (!input) return NaN; const onesMap = new Map(constants_1.ONES.map((w, i) => [w === '' ? 'zero' : w, i])); const teensMap = new Map(constants_1.TEENS.map((w, i) => [w, 10 + i])); const tensMap = new Map(constants_1.TENS.map((w, i) => [w, i * 10])); const scalesMap = new Map(constants_1.THOUSANDS.map((w, i) => [w, i === 0 ? 1 : 1000 ** i])); const PROTECTED_TOKENS = new Set([...constants_1.ONES, ...constants_1.TEENS, ...constants_1.TENS, ...constants_1.THOUSANDS, 'hundred', 'zero'].filter(Boolean)); const tokens = input .split(' ') .map((token) => { if (constants_1.ORDINAL_TO_CARDINAL[token]) { return constants_1.ORDINAL_TO_CARDINAL[token]; } if (PROTECTED_TOKENS.has(token)) { return token; } return token.replace(/(teenth|tieth|ieth|th|st|nd|rd)$/i, ''); }) .filter(Boolean); if (tokens.length === 0) return NaN; let total = 0; let currentNumber = 0; let hasInvalidToken = false; for (const token of tokens) { if (hasInvalidToken) break; if (onesMap.has(token)) { currentNumber += onesMap.get(token); continue; } if (teensMap.has(token)) { currentNumber += teensMap.get(token); continue; } if (tensMap.has(token)) { currentNumber += tensMap.get(token); continue; } if (token === 'hundred') { currentNumber = (currentNumber || 1) * 100; continue; } if (scalesMap.has(token)) { const scale = scalesMap.get(token); if (scale > 1) { total += (currentNumber || 1) * scale; currentNumber = 0; continue; } } if (/^\d+$/.test(token)) { currentNumber += Number(token); continue; } hasInvalidToken = true; } if (hasInvalidToken) return NaN; total += currentNumber; return negative ? -total : total; } function banglaToDigit(bnDigit, forceNumber = true) { if (!(0, primitives_1.isNonEmptyString)(bnDigit)) return (forceNumber ? NaN : ''); const digitStr = bnDigit.replace(/[০১২৩৪৫৬৭৮৯]/g, (d) => String(constants_1.BN_DIGITS[d])); if (forceNumber) { return Number(digitStr .split('') .filter((dig) => !Number.isNaN(Number(dig))) .join('')); } return digitStr; } function digitToBangla(digit, preserveNonDigit = true) { const banglaDigits = Object.keys(constants_1.BN_DIGITS); const _matchAndConvert = (value) => { return value.replace(/\d/g, (dig) => banglaDigits[Number(dig)]); }; if ((0, primitives_1.isNumber)(digit)) return _matchAndConvert(String(digit)); if ((0, primitives_1.isNonEmptyString)(digit)) { const bnDigStr = _matchAndConvert(digit); if (preserveNonDigit || (0, specials_1.isNumericString)(digit)) { return bnDigStr; } return bnDigStr .split('') .filter((dig) => banglaDigits.includes(dig)) .join(''); } return ''; }