|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- import {
- Group,
- GROUP_DIGITS_INDEX,
- GROUP_PLACE_INDEX,
- GroupPlace,
- } from '../../common';
- import { numberToExponential } from '../../exponent';
- import {
- CENTILLIONS_PREFIXES,
- CentillionsPrefix,
- DECILLIONS_PREFIXES,
- DecillionsPrefix,
- DECIMAL_POINT,
- EMPTY_GROUP_DIGITS,
- EXPONENT_DELIMITER,
- GROUPING_SYMBOL,
- HUNDRED,
- ILLION_SUFFIX,
- MILLIA_PREFIX,
- MILLIONS_PREFIXES,
- MILLIONS_SPECIAL_PREFIXES,
- MillionsPrefix,
- MillionsSpecialPrefix, NEGATIVE,
- ONES,
- OnesName,
- SHORT_MILLIA_DELIMITER,
- TEN_PLUS_ONES,
- TenPlusOnesName,
- TENS,
- TENS_ONES_SEPARATOR,
- TensName,
- THOUSAND,
- } from './common';
-
- /**
- * Builds a name for numbers in tens and ones.
- * @param tens - Tens digit.
- * @param ones - Ones digit.
- * @returns string The name for the number.
- */
- const makeTensName = (tens: number, ones: number) => {
- if (tens === 0) {
- return ONES[ones];
- }
-
- if (tens === 1) {
- return TEN_PLUS_ONES[ones] as unknown as TenPlusOnesName;
- }
-
- if (ones === 0) {
- return TENS[tens];
- }
-
- return `${TENS[tens] as Exclude<TensName, 'zero' | 'ten'>}${TENS_ONES_SEPARATOR}${ONES[ones] as Exclude<OnesName, 'zero'>}` as const;
- };
-
- /**
- * Builds a name for numbers in hundreds, tens, and ones.
- * @param hundreds - Hundreds digit.
- * @param tens - Tens digit.
- * @param ones - Ones digit.
- * @returns string The name for the number.
- */
- const makeHundredsName = (hundreds: number, tens: number, ones: number) => {
- if (hundreds === 0) {
- return makeTensName(tens, ones);
- }
-
- if (tens === 0 && ones === 0) {
- return `${ONES[hundreds]} ${HUNDRED}` as const;
- }
-
- return `${ONES[hundreds]} ${HUNDRED} ${makeTensName(tens, ones)}` as const;
- };
-
- /**
- * Builds a name for numbers in the millions.
- * @param millions - Millions digit.
- * @param milliaCount - Number of millia- groups.
- * @returns string The millions prefix.
- */
- const makeMillionsPrefix = (millions: number, milliaCount: GroupPlace) => {
- if (milliaCount > 0) {
- return MILLIONS_PREFIXES[millions] as MillionsPrefix;
- }
-
- return MILLIONS_SPECIAL_PREFIXES[millions] as MillionsSpecialPrefix;
- };
-
- /**
- * Builds a name for numbers in the decillions.
- * @param decillions - Decillions digit.
- * @param millions - Millions digit.
- * @param milliaCount - Number of millia- groups.
- * @returns string The decillions prefix.
- */
- const makeDecillionsPrefix = (decillions: number, millions: number, milliaCount: GroupPlace) => {
- if (decillions === 0) {
- return makeMillionsPrefix(millions, milliaCount);
- }
-
- const onesPrefix = MILLIONS_PREFIXES[millions] as MillionsPrefix;
- const tensName = DECILLIONS_PREFIXES[decillions] as DecillionsPrefix;
- return `${onesPrefix}${tensName}` as const;
- };
-
- /**
- * Builds a name for numbers in the centillions.
- * @param centillions - Centillions digit.
- * @param decillions - Decillions digit.
- * @param millions - Millions digit.
- * @param milliaCount - Number of millia- groups.
- * @returns string The centillions prefix.
- */
- const makeCentillionsPrefix = (
- centillions: number,
- decillions: number,
- millions: number,
- milliaCount: GroupPlace,
- ) => {
- if (centillions === 0) {
- return makeDecillionsPrefix(decillions, millions, milliaCount);
- }
-
- const onesPrefix = MILLIONS_PREFIXES[millions] as MillionsPrefix;
- const tensName = DECILLIONS_PREFIXES[decillions] as DecillionsPrefix;
- const hundredsName = CENTILLIONS_PREFIXES[centillions] as CentillionsPrefix;
- return `${hundredsName}${onesPrefix}${decillions > 0 ? tensName : ''}` as const;
- };
-
- const repeatString = (s: string, count: GroupPlace) => {
- let result = '';
- for (let i = BigInt(0); i < count; i += BigInt(1)) {
- result += s;
- }
- return result;
- };
-
- const getGroupName = (place: GroupPlace, shortenMillia: boolean) => {
- if (place === BigInt(0)) {
- return '' as const;
- }
-
- if (place === BigInt(1)) {
- return THOUSAND;
- }
-
- const bigGroupPlace = place - BigInt(1);
- const groupGroups = bigGroupPlace
- .toString()
- .split('')
- .reduceRight<Group[]>(
- (acc, c, i, cc) => {
- const firstGroup = acc.at(0);
- const currentPlace = BigInt(Math.floor((cc.length - i - 1) / 3));
- const newGroup = [EMPTY_GROUP_DIGITS, currentPlace] as Group;
- if (typeof firstGroup === 'undefined') {
- newGroup[GROUP_DIGITS_INDEX] = c;
- return [newGroup];
- }
-
- if (firstGroup[0].length > 2) {
- newGroup[GROUP_DIGITS_INDEX] = c;
- return [newGroup, ...acc];
- }
-
- newGroup[GROUP_DIGITS_INDEX] = c + firstGroup[0];
- return [
- newGroup,
- ...acc.slice(1),
- ];
- },
- [],
- )
- .map(([groupDigits, groupPlace]) => [groupDigits.padStart(3, '0'), groupPlace] as const)
- .filter(([groupDigits]) => groupDigits !== EMPTY_GROUP_DIGITS)
- .map(([groupDigits, groupPlace]) => {
- const [hundreds, tens, ones] = groupDigits.split('').map(Number);
- if (groupPlace < 1) {
- return makeCentillionsPrefix(hundreds, tens, ones, groupPlace);
- }
-
- const milliaSuffix = (
- shortenMillia && groupPlace > 1
- ? `${MILLIA_PREFIX}${SHORT_MILLIA_DELIMITER}${groupPlace}`
- : repeatString(MILLIA_PREFIX, groupPlace)
- );
-
- if (groupDigits === '001') {
- return milliaSuffix;
- }
-
- return makeCentillionsPrefix(hundreds, tens, ones, groupPlace) + milliaSuffix;
- })
- .join('');
-
- if (groupGroups.endsWith(DECILLIONS_PREFIXES[1])) {
- return `${groupGroups}${ILLION_SUFFIX}` as const;
- }
-
- if (bigGroupPlace > 10) {
- return `${groupGroups}t${ILLION_SUFFIX}` as const;
- }
-
- return `${groupGroups}${ILLION_SUFFIX}` as const;
- };
-
- export const makeGroups = (groups: Group[], options?: Record<string, unknown>): string[] => {
- const filteredGroups = groups.filter(([digits, place]) => (
- place === BigInt(0) || digits !== EMPTY_GROUP_DIGITS
- ));
-
- return filteredGroups.map(
- ([group, place]) => {
- const makeHundredsArgs = group
- .padStart(3, '0')
- .split('')
- .map((s) => Number(s)) as [number, number, number];
-
- const groupDigitsName = makeHundredsName(...makeHundredsArgs);
- const groupName = getGroupName(place, options?.shortenMillia as boolean ?? false);
- if (groupName.length > 0) {
- return `${groupDigitsName} ${groupName}`;
- }
- return groupDigitsName;
- },
- );
- };
-
- /**
- * Group a number string into groups of three digits, starting from the decimal point.
- * @param value - The number string to group.
- */
- export const group = (value: string): Group[] => {
- const [significand, exponentString] = numberToExponential(
- value,
- {
- decimalPoint: DECIMAL_POINT,
- groupingSymbol: GROUPING_SYMBOL,
- exponentDelimiter: EXPONENT_DELIMITER,
- },
- )
- .split(EXPONENT_DELIMITER);
- const exponent = Number(exponentString);
- const significantDigits = significand.replace(DECIMAL_POINT, '');
- return significantDigits.split('').reduce<Group[]>(
- (acc, c, i) => {
- const currentPlace = BigInt(Math.floor((exponent - i) / 3));
- const lastGroup = acc.at(-1) ?? [EMPTY_GROUP_DIGITS, currentPlace];
- const currentPlaceInGroup = 2 - ((exponent - i) % 3);
- if (lastGroup[GROUP_PLACE_INDEX] === currentPlace) {
- const lastGroupDigits = lastGroup[0].split('');
- lastGroupDigits[currentPlaceInGroup] = c;
- return [...acc.slice(0, -1) ?? [], [
- lastGroupDigits.join(''),
- currentPlace,
- ]];
- }
- return [...acc, [c.padEnd(3, '0'), currentPlace]];
- },
- [],
- );
- };
-
- /**
- * Formats the final tokenized string.
- * @param tokens - The tokens to finalize.
- */
- export const finalize = (tokens: string[]) => (
- tokens
- .map((t) => t.trim())
- .join(' ')
- .trim()
- );
-
- /**
- * Makes a negative string.
- * @param s - The string to make negative.
- */
- export const makeNegative = (s: string) => (
- `${NEGATIVE} ${s}`
- );
|