From a05bed5908da6338002287b04f3b917aaa207f0f Mon Sep 17 00:00:00 2001 From: TheoryOfNekomata Date: Mon, 21 Aug 2023 01:32:20 +0800 Subject: [PATCH] Implement long count Traditional British long count is available for systems. --- packages/core/src/common.ts | 20 + packages/core/src/converter.ts | 2 +- packages/core/src/systems/en-UK/index.ts | 1 + .../src/systems/en-UK/long-count/index.ts | 2 + .../src/systems/en-UK/long-count/parse.ts | 492 ++++++++++++++++++ .../src/systems/en-UK/long-count/stringify.ts | 345 ++++++++++++ .../src/systems/en-US/short-count/parse.ts | 23 +- .../systems/en-US/short-count/stringify.ts | 6 +- .../core/src/systems/{en-US => en}/common.ts | 0 .../test/{systems/en-US => }/chongo.test.ts | 172 +++--- .../test/systems/en-UK/long-count.test.ts | 343 ++++++++++++ .../test/systems/en-UK/short-count.test.ts | 334 ++++++++++++ packages/core/test/systems/en-US.test.ts | 304 ----------- .../test/systems/en-US/short-count.test.ts | 335 ++++++++++++ 14 files changed, 1966 insertions(+), 413 deletions(-) create mode 100644 packages/core/src/systems/en-UK/long-count/index.ts create mode 100644 packages/core/src/systems/en-UK/long-count/parse.ts create mode 100644 packages/core/src/systems/en-UK/long-count/stringify.ts rename packages/core/src/systems/{en-US => en}/common.ts (100%) rename packages/core/test/{systems/en-US => }/chongo.test.ts (80%) create mode 100644 packages/core/test/systems/en-UK/long-count.test.ts create mode 100644 packages/core/test/systems/en-UK/short-count.test.ts delete mode 100644 packages/core/test/systems/en-US.test.ts create mode 100644 packages/core/test/systems/en-US/short-count.test.ts diff --git a/packages/core/src/common.ts b/packages/core/src/common.ts index b69b2b1..e3cb9d5 100644 --- a/packages/core/src/common.ts +++ b/packages/core/src/common.ts @@ -108,3 +108,23 @@ export class InvalidTokenError extends Error { super(`Invalid token: ${token}`); } } + +export const bigIntMax = (...b: bigint[]) => b.reduce( + (previousMax, current) => { + if (typeof previousMax === 'undefined') { + return current; + } + return previousMax > current ? previousMax : current; + }, + undefined as bigint | undefined, +); + +export const bigIntMin = (...b: bigint[]) => b.reduce( + (previousMin, current) => { + if (typeof previousMin === 'undefined') { + return current; + } + return previousMin < current ? previousMin : current; + }, + undefined as bigint | undefined, +); diff --git a/packages/core/src/converter.ts b/packages/core/src/converter.ts index 37dde11..82e17e0 100644 --- a/packages/core/src/converter.ts +++ b/packages/core/src/converter.ts @@ -15,7 +15,7 @@ const EXPONENT_DELIMITER = 'e' as const; /** * Allowed value type for {@link stringify}. */ -type AllowedValue = string | number | bigint; +export type AllowedValue = string | number | bigint; /** * Array of allowed types for {@link parse}. diff --git a/packages/core/src/systems/en-UK/index.ts b/packages/core/src/systems/en-UK/index.ts index 656c7a2..abdf9f5 100644 --- a/packages/core/src/systems/en-UK/index.ts +++ b/packages/core/src/systems/en-UK/index.ts @@ -1 +1,2 @@ export * as shortCount from '../en-US/short-count'; +export * as longCount from './long-count'; diff --git a/packages/core/src/systems/en-UK/long-count/index.ts b/packages/core/src/systems/en-UK/long-count/index.ts new file mode 100644 index 0000000..7344573 --- /dev/null +++ b/packages/core/src/systems/en-UK/long-count/index.ts @@ -0,0 +1,2 @@ +export * from './parse'; +export * from './stringify'; diff --git a/packages/core/src/systems/en-UK/long-count/parse.ts b/packages/core/src/systems/en-UK/long-count/parse.ts new file mode 100644 index 0000000..c03c477 --- /dev/null +++ b/packages/core/src/systems/en-UK/long-count/parse.ts @@ -0,0 +1,492 @@ +import { + bigIntMax, bigIntMin, + Group, + GROUP_DIGITS_INDEX, + GROUP_PLACE_INDEX, + InvalidTokenError, +} from '../../../common'; +import { + CENTILLIONS_PREFIXES, + DECILLIONS_PREFIXES, + DECIMAL_POINT, + EMPTY_GROUP_DIGITS, + EMPTY_PLACE, + EXPONENT_DELIMITER, + HUNDRED, + ILLION_SUFFIX, + MILLIA_PREFIX, + MILLIONS_PREFIXES, + MILLIONS_SPECIAL_PREFIXES, + NEGATIVE_SYMBOL, + ONES, + OnesName, + POSITIVE_SYMBOL, + SHORT_MILLIA_DELIMITER, + SHORT_MILLIA_ILLION_DELIMITER, + T_AFFIX, + TEN_PLUS_ONES, + TenPlusOnesName, + TENS, + TENS_ONES_SEPARATOR, + TensName, + THOUSAND, +} from '../../en/common'; + +const FINAL_TOKEN = '' as const; + +/** + * Tokenizes a string. + * @param value - The string to tokenize. + * @see {NumberNameSystem.mergeTokens} + * @returns string[] The tokens. + */ +export const tokenize = (value: string) => ( + value + .toLowerCase() + .trim() + .replace(/\n+/gs, ' ') + .replace(/\s+/g, ' ') + .replace( + new RegExp(`${THOUSAND}\\s+(.+?${ILLION_SUFFIX})`, 'g'), + (_substring, illion: string) => ( + `${THOUSAND}${illion}` + ), + ) + .replace( + new RegExp(`${MILLIA_PREFIX}\\${SHORT_MILLIA_DELIMITER}(\\d+)${SHORT_MILLIA_ILLION_DELIMITER}`, 'g'), + (_substring, milliaCount: string) => `${MILLIA_PREFIX}${SHORT_MILLIA_DELIMITER}${milliaCount}`, + ) + .replace(new RegExp(`${TENS_ONES_SEPARATOR}`, 'g'), ' ') + .split(' ') + .filter((maybeToken) => maybeToken.length > 0) +); + +interface DoParseState { + groupNameCurrent: string; + millias: number[]; + milliaIndex: number; + done: boolean; +} + +/** + * Deconstructs a group name token (e.g. "million", "duodecillion", etc.) to its affixes and + * parses them. + * @param result - The current state of the parser. + * @returns DoParseState The next state of the parser. + */ +const doParseGroupName = (result: DoParseState): DoParseState => { + if ( + result.groupNameCurrent.length < 1 + + // If the current group name is "t", then we're done. + // We use the -t- affix to attach the group prefix to the -illion suffix, except for decillion. + || result.groupNameCurrent === T_AFFIX + ) { + return { + ...result, + + // Fill the gaps of millias with zeros. + millias: new Array(result.millias.length) + .fill(0) + .map((z, i) => ( + result.millias[i] ?? z + )), + done: true, + }; + } + + const centillions = CENTILLIONS_PREFIXES.findIndex((p) => ( + p.length > 0 && result.groupNameCurrent.startsWith(p) + )); + if (centillions > -1) { + return { + milliaIndex: 0, + millias: result.millias.map((m, i) => ( + i === 0 + ? m + (centillions * 100) + : m + )), + groupNameCurrent: result.groupNameCurrent.slice( + CENTILLIONS_PREFIXES[centillions].length, + ), + done: false, + }; + } + + const decillions = DECILLIONS_PREFIXES.findIndex((p) => ( + p.length > 0 && result.groupNameCurrent.startsWith(p) + )); + if (decillions > -1) { + return { + milliaIndex: 0, + millias: result.millias.map((m, i) => ( + i === 0 + ? m + (decillions * 10) + : m + )), + groupNameCurrent: result.groupNameCurrent.slice( + DECILLIONS_PREFIXES[decillions].length, + ), + done: false, + }; + } + + const millions = MILLIONS_PREFIXES.findIndex((p) => ( + p.length > 0 && result.groupNameCurrent.startsWith(p) + )); + if (millions > -1) { + return { + milliaIndex: 0, + millias: result.millias.map((m, i) => ( + i === 0 + ? m + millions + : m + )), + groupNameCurrent: result.groupNameCurrent.slice( + MILLIONS_PREFIXES[millions].length, + ), + done: false, + }; + } + + if (result.groupNameCurrent.startsWith(MILLIA_PREFIX)) { + let newMillia: number; + let prefix: string; + const isShortMillia = result.groupNameCurrent.startsWith(`${MILLIA_PREFIX}${SHORT_MILLIA_DELIMITER}`); + if (isShortMillia) { + const matchedMilliaArray = result.groupNameCurrent + .match(new RegExp(`^${MILLIA_PREFIX}\\${SHORT_MILLIA_DELIMITER}(\\d+)`)); + if (!matchedMilliaArray) { + throw new InvalidTokenError(result.groupNameCurrent); + } + const [wholeMilliaPrefix, matchedMillia] = matchedMilliaArray; + newMillia = Number(matchedMillia); + prefix = wholeMilliaPrefix; + } else { + newMillia = result.milliaIndex + 1; + prefix = MILLIA_PREFIX; + } + const oldMillia = result.milliaIndex; + const newMillias = [...result.millias]; + newMillias[newMillia] = newMillias[oldMillia] || 1; + newMillias[oldMillia] = 0; + return { + milliaIndex: newMillia, + millias: newMillias, + groupNameCurrent: result.groupNameCurrent.slice(prefix.length), + done: false, + }; + } + + throw new InvalidTokenError(result.groupNameCurrent); +}; + +/** + * Gets the place of a group name (e.g. "million", "duodecillion", etc.). + * @param groupName - The group name. + * @returns bigint The place of the group name. + */ +const getGroupPlaceFromGroupName = (groupName: string) => { + if (groupName === THOUSAND) { + return BigInt(1); + } + + const groupNameBase = groupName.replace(ILLION_SUFFIX, '').replace(THOUSAND, ''); + const specialMillions = MILLIONS_SPECIAL_PREFIXES.findIndex((p) => groupNameBase === p); + + if (specialMillions > -1) { + return BigInt(specialMillions * 2) + (groupName.startsWith(THOUSAND) ? BigInt(1) : BigInt(0)); + } + + let result: DoParseState = { + groupNameCurrent: groupNameBase, + millias: [0], + milliaIndex: 0, + done: false, + }; + + do { + result = doParseGroupName(result); + } while (!result.done); + + const bigGroupPlace = BigInt( + result.millias + .map((s) => s.toString().padStart(3, '0')) + .reverse() + .join(''), + ); + + return bigGroupPlace * BigInt(2) + (groupName.startsWith(THOUSAND) ? BigInt(1) : BigInt(0)); +}; + +/** + * Mode of the group parser. + */ +enum ParseGroupsMode { + /** + * Initial mode. + */ + INITIAL = 'initial', + /** + * Has parsed a ones name. + */ + ONES_MODE = 'ones', + /** + * Has parsed a tens name. + */ + TENS_MODE = 'tens', + /** + * Has parsed a ten-plus-ones name. + */ + TEN_PLUS_ONES_MODE = 'tenPlusOnes', + /** + * Has parsed a "hundred" token. + */ + HUNDRED_MODE = 'hundred', + /** + * Has parsed a "thousand" or any "-illion" token. + */ + THOUSAND_MODE = 'thousand', + /** + * Done parsing. + */ + DONE = 'done', +} + +/** + * State of the group parser. + */ +interface ParserState { + lastToken?: string; + groups: Group[]; + mode: ParseGroupsMode; +} + +const parseThousand = (acc: ParserState, token: string): ParserState => { + const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; + + if (acc.mode === ParseGroupsMode.ONES_MODE) { + const ones = ONES.findIndex((o) => o === acc.lastToken); + if (ones > -1) { + lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 2)}${ones}`; + } + } else if (acc.mode === ParseGroupsMode.TENS_MODE) { + const tens = TENS.findIndex((t) => t === acc.lastToken); + if (tens > -1) { + lastGroup[GROUP_DIGITS_INDEX] = ( + `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}${tens}${lastGroup[GROUP_DIGITS_INDEX].slice(2)}` + ); + } + } + + // Put the digits in the right place. + lastGroup[GROUP_PLACE_INDEX] = getGroupPlaceFromGroupName(token); + + return { + ...acc, + groups: [...acc.groups.slice(0, -1), lastGroup], + lastToken: token, + mode: ParseGroupsMode.THOUSAND_MODE, + }; +}; + +const parseHundred = (acc: ParserState): ParserState => { + const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; + const hundreds = ONES.findIndex((o) => o === acc.lastToken); + lastGroup[GROUP_DIGITS_INDEX] = `${hundreds}${lastGroup[GROUP_DIGITS_INDEX].slice(1)}`; + return { + ...acc, + groups: [...acc.groups.slice(0, -1), lastGroup], + mode: ParseGroupsMode.HUNDRED_MODE, + }; +}; + +const parseFinal = (acc: ParserState): ParserState => { + const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; + + if (acc.mode === ParseGroupsMode.ONES_MODE) { + const ones = ONES.findIndex((o) => o === acc.lastToken); + if (ones > -1) { + lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 2)}${ones}`; + } + // We assume last token without parsed place will always be the smallest + lastGroup[GROUP_PLACE_INDEX] = BigInt(0); + return { + ...acc, + groups: [...acc.groups.slice(0, -1), lastGroup], + mode: ParseGroupsMode.DONE, + }; + } + + if (acc.mode === ParseGroupsMode.TENS_MODE) { + const tens = TENS.findIndex((o) => o === acc.lastToken); + if (tens > -1) { + lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[0].slice(0, 1)}${tens}${lastGroup[GROUP_DIGITS_INDEX].slice(2)}`; + } + lastGroup[GROUP_PLACE_INDEX] = BigInt(0); + return { + ...acc, + groups: [...acc.groups.slice(0, -1), lastGroup], + mode: ParseGroupsMode.DONE, + }; + } + + return acc; +}; + +const parseOnes = (acc: ParserState, token: string): ParserState => { + if (acc.mode === ParseGroupsMode.THOUSAND_MODE) { + // Create next empty place + return { + ...acc, + lastToken: token, + mode: ParseGroupsMode.ONES_MODE, + groups: [...acc.groups, [...EMPTY_PLACE]], + }; + } + return { + ...acc, + lastToken: token, + mode: ParseGroupsMode.ONES_MODE, + }; +}; + +const parseTenPlusOnes = (acc: ParserState, token: string): ParserState => { + const tenPlusOnes = TEN_PLUS_ONES.findIndex((t) => t === token); + const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; + if (acc.mode === ParseGroupsMode.THOUSAND_MODE) { + return { + ...acc, + lastToken: token, + mode: ParseGroupsMode.TEN_PLUS_ONES_MODE, + groups: [...acc.groups, [`01${tenPlusOnes}`, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]], + }; + } + + lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}1${tenPlusOnes}`; + return { + ...acc, + lastToken: token, + mode: ParseGroupsMode.TEN_PLUS_ONES_MODE, + groups: [...acc.groups.slice(0, -1), lastGroup], + }; +}; + +const parseTens = (acc: ParserState, token: string): ParserState => { + const tens = TENS.findIndex((t) => t === token); + const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; + if (acc.mode === ParseGroupsMode.THOUSAND_MODE) { + return { + ...acc, + lastToken: token, + mode: ParseGroupsMode.TENS_MODE, + groups: [...acc.groups, [`0${tens}0`, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]], + }; + } + + lastGroup[GROUP_DIGITS_INDEX] = ( + `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}${tens}${lastGroup[GROUP_DIGITS_INDEX].slice(2)}` + ); + return { + ...acc, + lastToken: token, + mode: ParseGroupsMode.TENS_MODE, + groups: [...acc.groups.slice(0, -1), lastGroup], + }; +}; + +/** + * Parses groups from a string. + * @param tokens - The string to parse groups from. + * @see {NumberNameSystem.stringifyGroups} + * @returns Group[] The parsed groups. + */ +export const parseGroups = (tokens: string[]) => { + // We add a final token which is an empty string to parse whatever the last non-empty token is. + const tokensToParse = [...tokens, FINAL_TOKEN]; + const { groups } = tokensToParse.reduce( + (acc, token) => { + if (token === THOUSAND || token.endsWith(ILLION_SUFFIX)) { + return parseThousand(acc, token); + } + + if (token === HUNDRED && acc.mode === ParseGroupsMode.ONES_MODE) { + return parseHundred(acc); + } + + if (token === FINAL_TOKEN) { + return parseFinal(acc); + } + + if (ONES.includes(token as OnesName)) { + return parseOnes(acc, token); + } + + if (TEN_PLUS_ONES.includes(token as TenPlusOnesName)) { + return parseTenPlusOnes(acc, token); + } + + if (TENS.includes(token as TensName)) { + return parseTens(acc, token); + } + + return { + ...acc, + lastToken: token, + }; + }, + { + lastToken: undefined, + groups: [], + mode: ParseGroupsMode.INITIAL, + }, + ); + + return groups; +}; + +/** + * Combines groups into a string. + * @param groups - The groups to combine. + * @see {NumberNameSystem.splitIntoGroups} + * @returns string The combined groups in exponential form. + */ +export const combineGroups = (groups: Group[]) => { + const places = groups.map((g) => g[GROUP_PLACE_INDEX]); + if (places.length < 1) { + return ''; + } + const maxPlace = bigIntMax(...places) as bigint; + const minPlace = bigIntMin(...places) as bigint; + const firstGroup = groups.find((g) => g[GROUP_PLACE_INDEX] === maxPlace) ?? [...EMPTY_PLACE]; + const firstGroupPlace = firstGroup[GROUP_PLACE_INDEX]; + const groupsSorted = []; + for (let i = maxPlace; i >= minPlace; i = BigInt(i) - BigInt(1)) { + const thisGroup = groups.find((g) => g[GROUP_PLACE_INDEX] === i) ?? [EMPTY_GROUP_DIGITS, i]; + groupsSorted.push(thisGroup); + } + + const digits = groupsSorted.reduce( + (previousDigits, thisGroup) => { + const [groupDigits] = thisGroup; + return `${previousDigits}${groupDigits}`; + }, + '', + ).replace(/^0+/, '') || '0'; + const firstGroupDigits = firstGroup[0]; + const firstGroupDigitsWithoutZeroes = firstGroupDigits.replace(/^0+/, ''); + const exponentExtra = firstGroupDigits.length - firstGroupDigitsWithoutZeroes.length; + const exponentValue = BigInt( + (BigInt(firstGroupPlace) * BigInt(3)) + (BigInt(2) - BigInt(exponentExtra)), + ); + const isExponentNegative = exponentValue < 0; + const exponentValueAbs = isExponentNegative ? -exponentValue : exponentValue; + const exponentSign = isExponentNegative ? NEGATIVE_SYMBOL : POSITIVE_SYMBOL; + const exponent = `${exponentSign}${exponentValueAbs}`; + const significandInteger = digits.slice(0, 1); + const significandFraction = digits.slice(1).replace(/0+$/, ''); + if (significandFraction.length > 0) { + return `${significandInteger}${DECIMAL_POINT}${significandFraction}${EXPONENT_DELIMITER}${exponent}`; + } + return `${significandInteger}${EXPONENT_DELIMITER}${exponent}`; +}; diff --git a/packages/core/src/systems/en-UK/long-count/stringify.ts b/packages/core/src/systems/en-UK/long-count/stringify.ts new file mode 100644 index 0000000..96928af --- /dev/null +++ b/packages/core/src/systems/en-UK/long-count/stringify.ts @@ -0,0 +1,345 @@ +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, GROUP_SEPARATOR, + GROUPING_SYMBOL, + HUNDRED, + ILLION_SUFFIX, + MILLIA_PREFIX, + MILLIONS_PREFIXES, + MILLIONS_SPECIAL_PREFIXES, + MillionsPrefix, + MillionsSpecialPrefix, + NEGATIVE, + ONES, + OnesName, + SHORT_MILLIA_DELIMITER, SHORT_MILLIA_ILLION_DELIMITER, T_AFFIX, + TEN_PLUS_ONES, + TenPlusOnesName, + TENS, + TENS_ONES_SEPARATOR, + TensName, + THOUSAND, +} from '../../en/common'; + +/** + * Builds a name for numbers in tens and ones. + * @param tens - Tens digit. + * @param ones - Ones digit. + * @param addTensDashes - Whether to add dashes between the tens and ones. + * @returns string The name for the number. + */ +const makeTensName = (tens: number, ones: number, addTensDashes: boolean) => { + 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}${addTensDashes ? TENS_ONES_SEPARATOR : ' '}${ONES[ones] as Exclude}` as const; +}; + +/** + * Builds a name for numbers in hundreds, tens, and ones. + * @param hundreds - Hundreds digit. + * @param tens - Tens digit. + * @param ones - Ones digit. + * @param addTensDashes - Whether to add dashes between the tens and ones. + * @returns string The name for the number. + */ +const makeHundredsName = (hundreds: number, tens: number, ones: number, addTensDashes: boolean) => { + if (hundreds === 0) { + return makeTensName(tens, ones, addTensDashes); + } + + if (tens === 0 && ones === 0) { + return `${ONES[hundreds]} ${HUNDRED}` as const; + } + + return `${ONES[hundreds]} ${HUNDRED} ${makeTensName(tens, ones, addTensDashes)}` as const; +}; + +/** + * Builds a name for numbers in the millions. + * @param millions - Millions digit. + * @param currentMillia - Current millia- group. + * @param longestMilliaCount - Number of millia- groups. + * @returns string The millions prefix. + */ +const makeMillionsPrefix = ( + millions: number, + currentMillia: GroupPlace, + longestMilliaCount: GroupPlace, +) => { + if (currentMillia === BigInt(0) && longestMilliaCount === BigInt(0)) { + return MILLIONS_SPECIAL_PREFIXES[millions] as MillionsSpecialPrefix; + } + + return MILLIONS_PREFIXES[millions] as MillionsPrefix; +}; + +/** + * Builds a name for numbers in the decillions. + * @param decillions - Decillions digit. + * @param millions - Millions digit. + * @param currentMillia - Current millia- group. + * @param longestMilliaCount - Number of millia- groups. + * @returns string The decillions prefix. + */ +const makeDecillionsPrefix = ( + decillions: number, + millions: number, + currentMillia: GroupPlace, + longestMilliaCount: GroupPlace, +) => { + if (decillions === 0) { + return makeMillionsPrefix(millions, currentMillia, longestMilliaCount); + } + + 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 currentMillia - Current millia- group. + * @param longestMilliaCount - Number of millia- groups. + * @returns string The centillions prefix. + */ +const makeCentillionsPrefix = ( + centillions: number, + decillions: number, + millions: number, + currentMillia: GroupPlace, + longestMilliaCount: GroupPlace, +) => { + if (centillions === 0) { + return makeDecillionsPrefix(decillions, millions, currentMillia, longestMilliaCount); + } + + 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; +}; + +/** + * Repeats a string a given number of times. + * @param s - String to repeat. + * @param count - Number of times to repeat the string. + * @returns string The repeated string. + */ +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 isThousand = place % BigInt(2) === BigInt(1); + + const bigGroupPlace = place / BigInt(2); + const groupGroups = bigGroupPlace + .toString() + .split('') + .reduceRight( + (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], _index, millias) => { + const [hundreds, tens, ones] = groupDigits.split('').map(Number); + const largestMillia = millias[0][GROUP_PLACE_INDEX]; + + const centillionsPrefix = makeCentillionsPrefix( + hundreds, + tens, + ones, + groupPlace, + largestMillia, + ); + + if (groupPlace < 1) { + return centillionsPrefix; + } + + const milliaSuffix = ( + shortenMillia && groupPlace > 1 + ? `${MILLIA_PREFIX}${SHORT_MILLIA_DELIMITER}${groupPlace}${SHORT_MILLIA_ILLION_DELIMITER}` + : repeatString(MILLIA_PREFIX, groupPlace) + ); + + if (groupDigits === '001' && groupPlace === largestMillia) { + return milliaSuffix; + } + + return `${centillionsPrefix}${milliaSuffix}`; + }) + .join(''); + + if (groupGroups.endsWith(DECILLIONS_PREFIXES[1])) { + return `${isThousand ? `${THOUSAND} ` : ''}${groupGroups}${ILLION_SUFFIX}` as const; + } + + if (bigGroupPlace > 10) { + // vigin - t - illion, cen - t - illion, etc. + return `${isThousand ? `${THOUSAND} ` : ''}${groupGroups}${T_AFFIX}${ILLION_SUFFIX}` as const; + } + + return `${isThousand ? `${THOUSAND} ` : ''}${groupGroups}${ILLION_SUFFIX}` as const; +}; + +export interface StringifyGroupsOptions { + /** + * Whether to add dashes between tens and ones (e.g. "sixty-nine"). + */ + addTensDashes?: boolean; + /** + * Use "millia^2-tillion" instead of "milliamilliatillion". + */ + shortenMillia?: boolean; +} + +/** + * Creates a group string. + * @param groups - The groups. + * @param options - Options to use when creating the group. + * @returns string[] The groups represented into strings. + */ +export const stringifyGroups = (groups: Group[], options?: StringifyGroupsOptions): 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, + options?.addTensDashes ?? true, + ); + const groupName = getGroupName(place, options?.shortenMillia ?? 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. + * @returns Group[] The groups. + */ +export const splitIntoGroups = (value: string): Group[] => { + const [significand, exponentString] = numberToExponential( + value, + { + decimalPoint: DECIMAL_POINT, + groupingSymbol: GROUPING_SYMBOL, + exponentDelimiter: EXPONENT_DELIMITER, + }, + ) + .split(EXPONENT_DELIMITER); + // FIXME use bigint for exponent and indexing??? + const exponent = Number(exponentString); + const significantDigits = significand.replace(new RegExp(`\\${DECIMAL_POINT}`, 'g'), ''); + return significantDigits.split('').reduce( + (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]]; + }, + [], + ); +}; + +export interface MergeTokensOptions { + oneGroupPerLine?: boolean; +} + +/** + * Formats the final tokenized string. + * @param tokens - The tokens to finalize. + * @param options - The options to use. + */ +export const mergeTokens = (tokens: string[], options?: MergeTokensOptions) => ( + tokens + .map((t) => t.trim()) + .join(options?.oneGroupPerLine ? '\n' : GROUP_SEPARATOR) + .trim() +); + +/** + * Makes a negative string. + * @param s - The string to make negative. + */ +export const makeNegative = (s: string) => { + const negativePrefix = `${NEGATIVE} `; + return s.startsWith(negativePrefix) ? s.slice(negativePrefix.length) : `${negativePrefix}${s}`; +}; diff --git a/packages/core/src/systems/en-US/short-count/parse.ts b/packages/core/src/systems/en-US/short-count/parse.ts index 489a127..e3d496a 100644 --- a/packages/core/src/systems/en-US/short-count/parse.ts +++ b/packages/core/src/systems/en-US/short-count/parse.ts @@ -1,4 +1,5 @@ import { + bigIntMax, bigIntMin, Group, GROUP_DIGITS_INDEX, GROUP_PLACE_INDEX, @@ -29,7 +30,7 @@ import { TENS_ONES_SEPARATOR, TensName, THOUSAND, -} from '../common'; +} from '../../en/common'; const FINAL_TOKEN = '' as const; @@ -438,26 +439,6 @@ export const parseGroups = (tokens: string[]) => { return groups; }; -const bigIntMax = (...b: bigint[]) => b.reduce( - (previousMax, current) => { - if (typeof previousMax === 'undefined') { - return current; - } - return previousMax > current ? previousMax : current; - }, - undefined as bigint | undefined, -); - -const bigIntMin = (...b: bigint[]) => b.reduce( - (previousMin, current) => { - if (typeof previousMin === 'undefined') { - return current; - } - return previousMin < current ? previousMin : current; - }, - undefined as bigint | undefined, -); - /** * Combines groups into a string. * @param groups - The groups to combine. diff --git a/packages/core/src/systems/en-US/short-count/stringify.ts b/packages/core/src/systems/en-US/short-count/stringify.ts index 73ded72..19e2a08 100644 --- a/packages/core/src/systems/en-US/short-count/stringify.ts +++ b/packages/core/src/systems/en-US/short-count/stringify.ts @@ -31,7 +31,7 @@ import { TENS_ONES_SEPARATOR, TensName, THOUSAND, -} from '../common'; +} from '../../en/common'; /** * Builds a name for numbers in tens and ones. @@ -79,6 +79,7 @@ const makeHundredsName = (hundreds: number, tens: number, ones: number, addTensD /** * Builds a name for numbers in the millions. * @param millions - Millions digit. + * @param currentMillia - Current millia- group. * @param longestMilliaCount - Number of millia- groups. * @returns string The millions prefix. */ @@ -98,6 +99,7 @@ const makeMillionsPrefix = ( * Builds a name for numbers in the decillions. * @param decillions - Decillions digit. * @param millions - Millions digit. + * @param currentMillia - Current millia- group. * @param longestMilliaCount - Number of millia- groups. * @returns string The decillions prefix. */ @@ -121,6 +123,7 @@ const makeDecillionsPrefix = ( * @param centillions - Centillions digit. * @param decillions - Decillions digit. * @param millions - Millions digit. + * @param currentMillia - Current millia- group. * @param longestMilliaCount - Number of millia- groups. * @returns string The centillions prefix. */ @@ -292,6 +295,7 @@ export const splitIntoGroups = (value: string): Group[] => { }, ) .split(EXPONENT_DELIMITER); + // FIXME use bigint for exponent and indexing??? const exponent = Number(exponentString); const significantDigits = significand.replace(new RegExp(`\\${DECIMAL_POINT}`, 'g'), ''); return significantDigits.split('').reduce( diff --git a/packages/core/src/systems/en-US/common.ts b/packages/core/src/systems/en/common.ts similarity index 100% rename from packages/core/src/systems/en-US/common.ts rename to packages/core/src/systems/en/common.ts diff --git a/packages/core/test/systems/en-US/chongo.test.ts b/packages/core/test/chongo.test.ts similarity index 80% rename from packages/core/test/systems/en-US/chongo.test.ts rename to packages/core/test/chongo.test.ts index 3b67fa7..c38612e 100644 --- a/packages/core/test/systems/en-US/chongo.test.ts +++ b/packages/core/test/chongo.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; -import { stringify, parse } from '../../../src'; -import { numberToExponential } from '../../../src/exponent'; +import { stringify, parse } from '../src'; +import { numberToExponential } from '../src/exponent'; const stringifyOptions = { stringifyGroupsOptions: { @@ -11,17 +11,17 @@ const stringifyOptions = { describe('Landon\'s original test cases', () => { describe('Basic conversions', () => { it.each` - value | americanName - ${1} | ${'one'} - ${1000} | ${'one thousand'} - ${1000000} | ${'one million'} - ${1000000000} | ${'one billion'} - ${1000000000000} | ${'one trillion'} - ${1000000000000000} | ${'one quadrillion'} - ${1000000000000000000} | ${'one quintillion'} - `('converts $value to $americanName', ({ value, americanName }: { value: number, americanName: string }) => { - expect(stringify(value, stringifyOptions)).toBe(americanName); - expect(parse(americanName, { type: 'number' })).toBe(value); + value | numberName + ${'1e+0'} | ${'one'} + ${'1e+3'} | ${'one thousand'} + ${'1e+6'} | ${'one million'} + ${'1e+9'} | ${'one billion'} + ${'1e+12'} | ${'one trillion'} + ${'1e+15'} | ${'one quadrillion'} + ${'1e+18'} | ${'one quintillion'} + `('converts $value to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { + expect(stringify(value, stringifyOptions)).toBe(numberName); + expect(parse(numberName)).toBe(value); }); it( @@ -85,86 +85,86 @@ describe('Landon\'s original test cases', () => { describe('Medium size numbers (<= 1e+63)', () => { describe('Table 1', () => { it.each` - value | americanName - ${'1e+9'} | ${'billion'} - ${'1e+12'} | ${'trillion'} - ${'1e+15'} | ${'quadrillion'} - ${'1e+18'} | ${'quintillion'} - ${'1e+21'} | ${'sextillion'} - ${'1e+24'} | ${'septillion'} - ${'1e+27'} | ${'octillion'} - ${'1e+30'} | ${'nonillion'} - ${'1e+33'} | ${'decillion'} - ${'1e+36'} | ${'undecillion'} - ${'1e+39'} | ${'duodecillion'} - ${'1e+42'} | ${'tredecillion'} - ${'1e+45'} | ${'quattuordecillion'} - ${'1e+48'} | ${'quindecillion'} - ${'1e+51'} | ${'sexdecillion'} - ${'1e+54'} | ${'septendecillion'} - ${'1e+57'} | ${'octodecillion'} - ${'1e+60'} | ${'novemdecillion'} - ${'1e+63'} | ${'vigintillion'} - `('converts $value to $americanName', ({ value, americanName }: { value: string, americanName: string }) => { - expect(stringify(value, stringifyOptions)).toBe(`one ${americanName}`); - expect(parse(`one ${americanName}`)).toBe(value); + value | numberName + ${'1e+9'} | ${'one billion'} + ${'1e+12'} | ${'one trillion'} + ${'1e+15'} | ${'one quadrillion'} + ${'1e+18'} | ${'one quintillion'} + ${'1e+21'} | ${'one sextillion'} + ${'1e+24'} | ${'one septillion'} + ${'1e+27'} | ${'one octillion'} + ${'1e+30'} | ${'one nonillion'} + ${'1e+33'} | ${'one decillion'} + ${'1e+36'} | ${'one undecillion'} + ${'1e+39'} | ${'one duodecillion'} + ${'1e+42'} | ${'one tredecillion'} + ${'1e+45'} | ${'one quattuordecillion'} + ${'1e+48'} | ${'one quindecillion'} + ${'1e+51'} | ${'one sexdecillion'} + ${'1e+54'} | ${'one septendecillion'} + ${'1e+57'} | ${'one octodecillion'} + ${'1e+60'} | ${'one novemdecillion'} + ${'1e+63'} | ${'one vigintillion'} + `('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { + expect(stringify(value, stringifyOptions)).toBe(numberName); + expect(parse(numberName)).toBe(value); }); }); }); describe('Large size numbers (< 1e+303)', () => { it.each` - value | americanName - ${'1e+66'} | ${'unvigintillion'} - ${'1e+69'} | ${'duovigintillion'} - ${'1e+72'} | ${'trevigintillion'} - ${'1e+75'} | ${'quattuorvigintillion'} - ${'1e+78'} | ${'quinvigintillion'} - ${'1e+81'} | ${'sexvigintillion'} - ${'1e+84'} | ${'septenvigintillion'} - ${'1e+87'} | ${'octovigintillion'} - ${'1e+90'} | ${'novemvigintillion'} - ${'1e+93'} | ${'trigintillion'} - ${'1e+123'} | ${'quadragintillion'} - ${'1e+150'} | ${'novemquadragintillion'} - ${'1e+153'} | ${'quinquagintillion'} - ${'1e+156'} | ${'unquinquagintillion'} - ${'1e+183'} | ${'sexagintillion'} - ${'1e+213'} | ${'septuagintillion'} - ${'1e+222'} | ${'treseptuagintillion'} - ${'1e+243'} | ${'octogintillion'} - ${'1e+273'} | ${'nonagintillion'} - ${'1e+300'} | ${'novemnonagintillion'} - `('converts $value to $americanName', ({ value, americanName }: { value: string, americanName: string }) => { - expect(stringify(value, stringifyOptions)).toBe(`one ${americanName}`); - expect(parse(`one ${americanName}`)).toBe(value); + value | numberName + ${'1e+66'} | ${'one unvigintillion'} + ${'1e+69'} | ${'one duovigintillion'} + ${'1e+72'} | ${'one trevigintillion'} + ${'1e+75'} | ${'one quattuorvigintillion'} + ${'1e+78'} | ${'one quinvigintillion'} + ${'1e+81'} | ${'one sexvigintillion'} + ${'1e+84'} | ${'one septenvigintillion'} + ${'1e+87'} | ${'one octovigintillion'} + ${'1e+90'} | ${'one novemvigintillion'} + ${'1e+93'} | ${'one trigintillion'} + ${'1e+123'} | ${'one quadragintillion'} + ${'1e+150'} | ${'one novemquadragintillion'} + ${'1e+153'} | ${'one quinquagintillion'} + ${'1e+156'} | ${'one unquinquagintillion'} + ${'1e+183'} | ${'one sexagintillion'} + ${'1e+213'} | ${'one septuagintillion'} + ${'1e+222'} | ${'one treseptuagintillion'} + ${'1e+243'} | ${'one octogintillion'} + ${'1e+273'} | ${'one nonagintillion'} + ${'1e+300'} | ${'one novemnonagintillion'} + `('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { + expect(stringify(value, stringifyOptions)).toBe(numberName); + expect(parse(numberName)).toBe(value); }); }); describe('Gigantic size numbers (< 1e+3003)', () => { it.each` - value | americanName - ${'1e+303'} | ${'centillion'} - ${'1e+306'} | ${'cenuntillion'} - ${'1e+309'} | ${'cenduotillion'} - ${'1e+312'} | ${'centretillion'} - ${'1e+315'} | ${'cenquattuortillion'} - ${'1e+318'} | ${'cenquintillion'} - ${'1e+321'} | ${'censextillion'} - ${'1e+324'} | ${'censeptentillion'} - ${'1e+327'} | ${'cenoctotillion'} - ${'1e+330'} | ${'cennovemtillion'} - ${'1e+603'} | ${'duocentillion'} - ${'1e+903'} | ${'trecentillion'} - ${'1e+1203'} | ${'quadringentillion'} - ${'1e+1503'} | ${'quingentillion'} - ${'1e+1803'} | ${'sescentillion'} - ${'1e+2103'} | ${'septingentillion'} - ${'1e+2403'} | ${'octingentillion'} - ${'1e+2703'} | ${'nongentillion'} - `('converts $value to $americanName', ({ value, americanName }: { value: string, americanName: string }) => { - expect(stringify(value, stringifyOptions)).toBe(`one ${americanName}`); - expect(parse(`one ${americanName}`)).toBe(value); + value | numberName + ${'1e+303'} | ${'one centillion'} + ${'1e+306'} | ${'one cenuntillion'} + ${'1e+309'} | ${'one cenduotillion'} + ${'1e+312'} | ${'one centretillion'} + ${'1e+315'} | ${'one cenquattuortillion'} + ${'1e+318'} | ${'one cenquintillion'} + ${'1e+321'} | ${'one censextillion'} + ${'1e+324'} | ${'one censeptentillion'} + ${'1e+327'} | ${'one cenoctotillion'} + ${'1e+330'} | ${'one cennovemtillion'} + ${'1e+603'} | ${'one duocentillion'} + ${'1e+903'} | ${'one trecentillion'} + ${'1e+1203'} | ${'one quadringentillion'} + ${'1e+1503'} | ${'one quingentillion'} + ${'1e+1803'} | ${'one sescentillion'} + ${'1e+2103'} | ${'one septingentillion'} + ${'1e+2403'} | ${'one octingentillion'} + ${'1e+2703'} | ${'one nongentillion'} + `('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { + expect(stringify(value, stringifyOptions)).toBe(numberName); + expect(parse(numberName)).toBe(value); }); }); @@ -183,7 +183,7 @@ describe('Landon\'s original test cases', () => { ); it.each` - value | americanName + value | numberName ${'1.23456789246801357e+6221'} | ${'one hundred twenty three duomilliaduoseptuagintillion four hundred fifty six duomilliaunseptuagintillion seven hundred eighty nine duomilliaseptuagintillion two hundred forty six duomillianovemsexagintillion eight hundred one duomilliaoctosexagintillion three hundred fifty seven duomilliaseptensexagintillion'} ${'1.23456789246801357e+2961221'} | ${'one hundred twenty three nongenseptenoctoginmilliaduoseptuagintillion four hundred fifty six nongenseptenoctoginmilliaunseptuagintillion seven hundred eighty nine nongenseptenoctoginmilliaseptuagintillion two hundred forty six nongenseptenoctoginmillianovemsexagintillion eight hundred one nongenseptenoctoginmilliaoctosexagintillion three hundred fifty seven nongenseptenoctoginmilliaseptensexagintillion'} ${'123.456789246801357e+2961000000219'} | ${'one hundred twenty three nongenseptenoctoginmilliamilliamilliaduoseptuagintillion four hundred fifty six nongenseptenoctoginmilliamilliamilliaunseptuagintillion seven hundred eighty nine nongenseptenoctoginmilliamilliamilliaseptuagintillion two hundred forty six nongenseptenoctoginmilliamilliamillianovemsexagintillion eight hundred one nongenseptenoctoginmilliamilliamilliaoctosexagintillion three hundred fifty seven nongenseptenoctoginmilliamilliamilliaseptensexagintillion'} @@ -195,9 +195,9 @@ describe('Landon\'s original test cases', () => { ${'1e+1174743648579'} | ${'one trecenunnonaginmilliamilliamilliaquingenunoctoginmilliamilliaduocensexdecmilliacenduononagintillion'} ${'1e+3000000000003'} | ${'one milliamilliamilliamilliatillion'} ${'1e+696276510359811'} | ${'one duocenduotriginmilliamilliamilliamilliaduononaginmilliamilliamilliacenseptuaginmilliamilliacennovemdecmillianongensextrigintillion'} - `('converts $value to $americanName', ({ value, americanName }: { value: string, americanName: string }) => { - expect(stringify(value, stringifyOptions)).toBe(americanName); - expect(parse(americanName)).toBe(numberToExponential(value)); + `('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { + expect(stringify(value, stringifyOptions)).toBe(numberName); + expect(parse(numberName)).toBe(numberToExponential(value)); }); }); }); diff --git a/packages/core/test/systems/en-UK/long-count.test.ts b/packages/core/test/systems/en-UK/long-count.test.ts new file mode 100644 index 0000000..8d227a6 --- /dev/null +++ b/packages/core/test/systems/en-UK/long-count.test.ts @@ -0,0 +1,343 @@ +import { describe, it, expect } from 'vitest'; +import { + AllowedValue, + parse, + stringify, + systems, +} from '../../../src'; + +const options = { + system: systems.enUK.longCount, +}; + +const doExpect = ( + value: AllowedValue, + stringified: string, + parsedValue: AllowedValue = value, +) => { + const stringifyOptions = { + ...options, + }; + + const parseOptions = { + ...options, + type: typeof value as ('number' | 'string' | 'bigint'), + }; + + expect(stringify(value, stringifyOptions)).toBe(stringified); + expect(parse(stringified, parseOptions)).toBe(parsedValue); +}; + +describe('British long count', () => { + describe('individual cases', () => { + const longNumberValue1 = '123,456,789,012,345,678,901,234,567,890'; + const longNumberStringified1 = [ + 'one hundred twenty-three thousand quadrillion', + 'four hundred fifty-six quadrillion', + 'seven hundred eighty-nine thousand trillion', + 'twelve trillion', + 'three hundred forty-five thousand billion', + 'six hundred seventy-eight billion', + 'nine hundred one thousand million', + 'two hundred thirty-four million', + 'five hundred sixty-seven thousand', + 'eight hundred ninety', + ].join(' '); + + const longNumberParsedValue = '1.2345678901234567890123456789e+29'; + + it.each` + value | stringified | parsedValue + ${1000} | ${'one thousand'} | ${1000} + ${10000} | ${'ten thousand'} | ${10000} + ${12012} | ${'twelve thousand twelve'} | ${12012} + ${12020} | ${'twelve thousand twenty'} | ${12020} + ${100000} | ${'one hundred thousand'} | ${100000} + ${123456} | ${'one hundred twenty-three thousand four hundred fifty-six'} | ${123456} + ${'1000005000000'} | ${'one billion five million'} | ${'1.000005e+12'} + ${longNumberValue1} | ${longNumberStringified1} | ${longNumberParsedValue} + ${'1e6006'} | ${'one milliauntillion'} | ${'1e+6006'} + `('converts $value to $stringified', ({ + value, + stringified, + parsedValue, + }: { value: AllowedValue, stringified: string, parsedValue: AllowedValue }) => { + doExpect(value, stringified, parsedValue); + }); + + it('converts one millia^1-tillion to 1e+3003', () => { + expect(parse('one millia^1-tillion')).toBe('1e+3003'); + }); + }); + + describe('blanket cases', () => { + it('converts one millia^2-tillion', () => { + const value = '1e+3000003'; + const stringified = 'one millia^2-tillion'; + expect(parse(stringified)).toBe(value); + expect(stringify(value, { stringifyGroupsOptions: { shortenMillia: true } })) + .toBe(stringified); + }); + + it('converts one millia^2-unmilliatillion', () => { + const value = '1e+3003003'; + const stringified = 'one millia^2-unmilliatillion'; + expect(stringify(value, { stringifyGroupsOptions: { shortenMillia: true } })) + .toBe(stringified); + expect(parse(stringified)).toBe(value); + }); + + it('converts one milliamilliaunmilliatillion', () => { + const value = '1e+3003003'; + const stringified = 'one milliamilliaunmilliatillion'; + expect(stringify(value)).toBe(stringified); + expect(parse(stringified)).toBe('1e+3003003'); + }); + }); + + describe('general', () => { + describe('0-9', () => { + it.each` + value | numberName + ${0} | ${'zero'} + ${1} | ${'one'} + ${2} | ${'two'} + ${3} | ${'three'} + ${4} | ${'four'} + ${5} | ${'five'} + ${6} | ${'six'} + ${7} | ${'seven'} + ${8} | ${'eight'} + ${9} | ${'nine'} + `('converts $ones to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { + doExpect(value, numberName); + }); + }); + + describe('10-19', () => { + it.each` + value | numberName + ${10} | ${'ten'} + ${11} | ${'eleven'} + ${12} | ${'twelve'} + ${13} | ${'thirteen'} + ${14} | ${'fourteen'} + ${15} | ${'fifteen'} + ${16} | ${'sixteen'} + ${17} | ${'seventeen'} + ${18} | ${'eighteen'} + ${19} | ${'nineteen'} + `('converts $tenPlusOnes to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { + doExpect(value, numberName); + }); + }); + + describe.each` + tensStart | tensEnd | tensBase + ${20} | ${29} | ${'twenty'} + ${30} | ${39} | ${'thirty'} + ${40} | ${49} | ${'forty'} + ${50} | ${59} | ${'fifty'} + ${60} | ${69} | ${'sixty'} + ${70} | ${79} | ${'seventy'} + ${80} | ${89} | ${'eighty'} + ${90} | ${99} | ${'ninety'} + `('$tensStart-$tensEnd', ({ + tensStart, tensBase, + }: { tensStart: number, tensBase: string }) => { + it.each` + value | numberName + ${tensStart} | ${tensBase} + ${tensStart + 1} | ${`${tensBase}-one`} + ${tensStart + 2} | ${`${tensBase}-two`} + ${tensStart + 3} | ${`${tensBase}-three`} + ${tensStart + 4} | ${`${tensBase}-four`} + ${tensStart + 5} | ${`${tensBase}-five`} + ${tensStart + 6} | ${`${tensBase}-six`} + ${tensStart + 7} | ${`${tensBase}-seven`} + ${tensStart + 8} | ${`${tensBase}-eight`} + ${tensStart + 9} | ${`${tensBase}-nine`} + `('converts $value to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { + doExpect(value, numberName); + }); + }); + + describe.each` + hundredsStart | hundredsEnd | hundredsBase + ${100} | ${199} | ${'one hundred'} + ${200} | ${299} | ${'two hundred'} + ${300} | ${399} | ${'three hundred'} + ${400} | ${499} | ${'four hundred'} + ${500} | ${599} | ${'five hundred'} + ${600} | ${699} | ${'six hundred'} + ${700} | ${799} | ${'seven hundred'} + ${800} | ${899} | ${'eight hundred'} + ${900} | ${999} | ${'nine hundred'} + `('$hundredsStart-$hundredsEnd', ({ + hundredsStart, hundredsBase, + }: { hundredsStart: number, hundredsBase: string }) => { + describe(`${hundredsStart}-${hundredsStart + 9}`, () => { + it.each` + value | numberName + ${hundredsStart} | ${hundredsBase} + ${hundredsStart + 1} | ${`${hundredsBase} one`} + ${hundredsStart + 2} | ${`${hundredsBase} two`} + ${hundredsStart + 3} | ${`${hundredsBase} three`} + ${hundredsStart + 4} | ${`${hundredsBase} four`} + ${hundredsStart + 5} | ${`${hundredsBase} five`} + ${hundredsStart + 6} | ${`${hundredsBase} six`} + ${hundredsStart + 7} | ${`${hundredsBase} seven`} + ${hundredsStart + 8} | ${`${hundredsBase} eight`} + ${hundredsStart + 9} | ${`${hundredsBase} nine`} + `('converts $value to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { + doExpect(value, numberName); + }); + }); + + describe(`${hundredsStart + 10}-${hundredsStart + 19}`, () => { + it.each` + value | numberName + ${hundredsStart + 10} | ${`${hundredsBase} ten`} + ${hundredsStart + 11} | ${`${hundredsBase} eleven`} + ${hundredsStart + 12} | ${`${hundredsBase} twelve`} + ${hundredsStart + 13} | ${`${hundredsBase} thirteen`} + ${hundredsStart + 14} | ${`${hundredsBase} fourteen`} + ${hundredsStart + 15} | ${`${hundredsBase} fifteen`} + ${hundredsStart + 16} | ${`${hundredsBase} sixteen`} + ${hundredsStart + 17} | ${`${hundredsBase} seventeen`} + ${hundredsStart + 18} | ${`${hundredsBase} eighteen`} + ${hundredsStart + 19} | ${`${hundredsBase} nineteen`} + `('converts $value to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { + doExpect(value, numberName); + }); + }); + + describe.each` + start | end | base + ${20} | ${29} | ${'twenty'} + ${30} | ${39} | ${'thirty'} + ${40} | ${49} | ${'forty'} + ${50} | ${59} | ${'fifty'} + ${60} | ${69} | ${'sixty'} + ${70} | ${79} | ${'seventy'} + ${80} | ${89} | ${'eighty'} + ${90} | ${99} | ${'ninety'} + `('$start-$end', ({ + start, base, + }: { start: number, base: string }) => { + it.each` + value | numberName + ${hundredsStart + start} | ${`${hundredsBase} ${base}`} + ${hundredsStart + start + 1} | ${`${hundredsBase} ${base}-one`} + ${hundredsStart + start + 2} | ${`${hundredsBase} ${base}-two`} + ${hundredsStart + start + 3} | ${`${hundredsBase} ${base}-three`} + ${hundredsStart + start + 4} | ${`${hundredsBase} ${base}-four`} + ${hundredsStart + start + 5} | ${`${hundredsBase} ${base}-five`} + ${hundredsStart + start + 6} | ${`${hundredsBase} ${base}-six`} + ${hundredsStart + start + 7} | ${`${hundredsBase} ${base}-seven`} + ${hundredsStart + start + 8} | ${`${hundredsBase} ${base}-eight`} + ${hundredsStart + start + 9} | ${`${hundredsBase} ${base}-nine`} + `('converts $value to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { + doExpect(value, numberName); + }); + }); + }); + }); + + it.each` + value | numberName + ${'1e+6'} | ${'one million'} + ${'1e+9'} | ${'one thousand million'} + ${'1e+12'} | ${'one billion'} + ${'1e+15'} | ${'one thousand billion'} + ${'1e+18'} | ${'one trillion'} + ${'1e+21'} | ${'one thousand trillion'} + ${'1e+24'} | ${'one quadrillion'} + ${'1e+27'} | ${'one thousand quadrillion'} + ${'1e+30'} | ${'one quintillion'} + ${'1e+33'} | ${'one thousand quintillion'} + ${'1e+36'} | ${'one sextillion'} + ${'1e+39'} | ${'one thousand sextillion'} + ${'1e+42'} | ${'one septillion'} + ${'1e+45'} | ${'one thousand septillion'} + ${'1e+48'} | ${'one octillion'} + ${'1e+51'} | ${'one thousand octillion'} + ${'1e+54'} | ${'one nonillion'} + ${'1e+57'} | ${'one thousand nonillion'} + `('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { + doExpect(value, numberName); + }); + + it.each` + value | numberName + ${'1e+60'} | ${'one decillion'} + ${'1e+66'} | ${'one undecillion'} + ${'1e+72'} | ${'one duodecillion'} + ${'1e+78'} | ${'one tredecillion'} + ${'1e+84'} | ${'one quattuordecillion'} + ${'1e+90'} | ${'one quindecillion'} + ${'1e+96'} | ${'one sexdecillion'} + ${'1e+102'} | ${'one septendecillion'} + ${'1e+108'} | ${'one octodecillion'} + ${'1e+114'} | ${'one novemdecillion'} + `('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { + doExpect(value, numberName); + }); + + it.each` + value | numberName + ${'1e+120'} | ${'one vigintillion'} + ${'1e+126'} | ${'one unvigintillion'} + ${'1e+132'} | ${'one duovigintillion'} + ${'1e+138'} | ${'one trevigintillion'} + ${'1e+144'} | ${'one quattuorvigintillion'} + ${'1e+150'} | ${'one quinvigintillion'} + ${'1e+156'} | ${'one sexvigintillion'} + ${'1e+162'} | ${'one septenvigintillion'} + ${'1e+168'} | ${'one octovigintillion'} + ${'1e+174'} | ${'one novemvigintillion'} + `('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { + doExpect(value, numberName); + }); + + it.each` + value | numberName + ${'1e+180'} | ${'one trigintillion'} + ${'1e+240'} | ${'one quadragintillion'} + ${'1e+300'} | ${'one quinquagintillion'} + ${'1e+360'} | ${'one sexagintillion'} + ${'1e+420'} | ${'one septuagintillion'} + ${'1e+480'} | ${'one octogintillion'} + ${'1e+540'} | ${'one nonagintillion'} + `('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { + doExpect(value, numberName); + }); + + it.each` + value | numberName + ${'1e+600'} | ${'one centillion'} + ${'1e+606'} | ${'one cenuntillion'} + ${'1e+612'} | ${'one cenduotillion'} + ${'1e+618'} | ${'one centretillion'} + ${'1e+624'} | ${'one cenquattuortillion'} + ${'1e+630'} | ${'one cenquintillion'} + ${'1e+636'} | ${'one censextillion'} + ${'1e+642'} | ${'one censeptentillion'} + ${'1e+648'} | ${'one cenoctotillion'} + ${'1e+654'} | ${'one cennovemtillion'} + ${'1e+1200'} | ${'one duocentillion'} + ${'1e+1800'} | ${'one trecentillion'} + ${'1e+2400'} | ${'one quadringentillion'} + ${'1e+3000'} | ${'one quingentillion'} + ${'1e+3600'} | ${'one sescentillion'} + ${'1e+4200'} | ${'one septingentillion'} + ${'1e+4800'} | ${'one octingentillion'} + ${'1e+5400'} | ${'one nongentillion'} + `('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { + doExpect(value, numberName); + }); + + it('converts \'1e+6000\' to \'one milliatillion\'', () => { + doExpect('1e+6000', 'one milliatillion'); + }); +}); diff --git a/packages/core/test/systems/en-UK/short-count.test.ts b/packages/core/test/systems/en-UK/short-count.test.ts new file mode 100644 index 0000000..2f1cb08 --- /dev/null +++ b/packages/core/test/systems/en-UK/short-count.test.ts @@ -0,0 +1,334 @@ +import { describe, it, expect } from 'vitest'; +import { + AllowedValue, + parse, + stringify, + systems, +} from '../../../src'; + +const options = { + system: systems.enUK.shortCount, +}; + +const doExpect = ( + value: AllowedValue, + stringified: string, + parsedValue: AllowedValue = value, +) => { + const stringifyOptions = { + ...options, + }; + + const parseOptions = { + ...options, + type: typeof value as ('number' | 'string' | 'bigint'), + }; + + expect(stringify(value, stringifyOptions)).toBe(stringified); + expect(parse(stringified, parseOptions)).toBe(parsedValue); +}; + +describe('British short count', () => { + describe('individual cases', () => { + const longNumberValue1 = '123,456,789,012,345,678,901,234,567,890'; + const longNumberStringified1 = [ + 'one hundred twenty-three octillion', + 'four hundred fifty-six septillion', + 'seven hundred eighty-nine sextillion', + 'twelve quintillion', + 'three hundred forty-five quadrillion', + 'six hundred seventy-eight trillion', + 'nine hundred one billion', + 'two hundred thirty-four million', + 'five hundred sixty-seven thousand', + 'eight hundred ninety', + ].join(' '); + + const longNumberParsedValue = '1.2345678901234567890123456789e+29'; + + it.each` + value | stringified | parsedValue + ${1000} | ${'one thousand'} | ${1000} + ${10000} | ${'ten thousand'} | ${10000} + ${12012} | ${'twelve thousand twelve'} | ${12012} + ${12020} | ${'twelve thousand twenty'} | ${12020} + ${100000} | ${'one hundred thousand'} | ${100000} + ${123456} | ${'one hundred twenty-three thousand four hundred fifty-six'} | ${123456} + ${'1000005000000'} | ${'one trillion five million'} | ${'1.000005e+12'} + ${longNumberValue1} | ${longNumberStringified1} | ${longNumberParsedValue} + ${'1e3006'} | ${'one milliauntillion'} | ${'1e+3006'} + `('converts $value to $stringified', ({ + value, + stringified, + parsedValue, + }: { value: AllowedValue, stringified: string, parsedValue: AllowedValue }) => { + doExpect(value, stringified, parsedValue); + }); + + it('converts one millia^1-tillion to 1e+3003', () => { + expect(parse('one millia^1-tillion')).toBe('1e+3003'); + }); + }); + + describe('blanket cases', () => { + it('converts one millia^2-tillion', () => { + const value = '1e+3000003'; + const stringified = 'one millia^2-tillion'; + expect(parse(stringified)).toBe(value); + expect(stringify(value, { stringifyGroupsOptions: { shortenMillia: true } })) + .toBe(stringified); + }); + + it('converts one millia^2-unmilliatillion', () => { + const value = '1e+3003003'; + const stringified = 'one millia^2-unmilliatillion'; + expect(stringify(value, { stringifyGroupsOptions: { shortenMillia: true } })) + .toBe(stringified); + expect(parse(stringified)).toBe(value); + }); + + it('converts one milliamilliaunmilliatillion', () => { + const value = '1e+3003003'; + const stringified = 'one milliamilliaunmilliatillion'; + expect(stringify(value)).toBe(stringified); + expect(parse(stringified)).toBe('1e+3003003'); + }); + }); + + describe('general', () => { + describe('0-9', () => { + it.each` + value | numberName + ${0} | ${'zero'} + ${1} | ${'one'} + ${2} | ${'two'} + ${3} | ${'three'} + ${4} | ${'four'} + ${5} | ${'five'} + ${6} | ${'six'} + ${7} | ${'seven'} + ${8} | ${'eight'} + ${9} | ${'nine'} + `('converts $ones to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { + doExpect(value, numberName); + }); + }); + + describe('10-19', () => { + it.each` + value | numberName + ${10} | ${'ten'} + ${11} | ${'eleven'} + ${12} | ${'twelve'} + ${13} | ${'thirteen'} + ${14} | ${'fourteen'} + ${15} | ${'fifteen'} + ${16} | ${'sixteen'} + ${17} | ${'seventeen'} + ${18} | ${'eighteen'} + ${19} | ${'nineteen'} + `('converts $tenPlusOnes to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { + doExpect(value, numberName); + }); + }); + + describe.each` + tensStart | tensEnd | tensBase + ${20} | ${29} | ${'twenty'} + ${30} | ${39} | ${'thirty'} + ${40} | ${49} | ${'forty'} + ${50} | ${59} | ${'fifty'} + ${60} | ${69} | ${'sixty'} + ${70} | ${79} | ${'seventy'} + ${80} | ${89} | ${'eighty'} + ${90} | ${99} | ${'ninety'} + `('$tensStart-$tensEnd', ({ + tensStart, tensBase, + }: { tensStart: number, tensBase: string }) => { + it.each` + value | numberName + ${tensStart} | ${tensBase} + ${tensStart + 1} | ${`${tensBase}-one`} + ${tensStart + 2} | ${`${tensBase}-two`} + ${tensStart + 3} | ${`${tensBase}-three`} + ${tensStart + 4} | ${`${tensBase}-four`} + ${tensStart + 5} | ${`${tensBase}-five`} + ${tensStart + 6} | ${`${tensBase}-six`} + ${tensStart + 7} | ${`${tensBase}-seven`} + ${tensStart + 8} | ${`${tensBase}-eight`} + ${tensStart + 9} | ${`${tensBase}-nine`} + `('converts $value to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { + doExpect(value, numberName); + }); + }); + + describe.each` + hundredsStart | hundredsEnd | hundredsBase + ${100} | ${199} | ${'one hundred'} + ${200} | ${299} | ${'two hundred'} + ${300} | ${399} | ${'three hundred'} + ${400} | ${499} | ${'four hundred'} + ${500} | ${599} | ${'five hundred'} + ${600} | ${699} | ${'six hundred'} + ${700} | ${799} | ${'seven hundred'} + ${800} | ${899} | ${'eight hundred'} + ${900} | ${999} | ${'nine hundred'} + `('$hundredsStart-$hundredsEnd', ({ + hundredsStart, hundredsBase, + }: { hundredsStart: number, hundredsBase: string }) => { + describe(`${hundredsStart}-${hundredsStart + 9}`, () => { + it.each` + value | numberName + ${hundredsStart} | ${hundredsBase} + ${hundredsStart + 1} | ${`${hundredsBase} one`} + ${hundredsStart + 2} | ${`${hundredsBase} two`} + ${hundredsStart + 3} | ${`${hundredsBase} three`} + ${hundredsStart + 4} | ${`${hundredsBase} four`} + ${hundredsStart + 5} | ${`${hundredsBase} five`} + ${hundredsStart + 6} | ${`${hundredsBase} six`} + ${hundredsStart + 7} | ${`${hundredsBase} seven`} + ${hundredsStart + 8} | ${`${hundredsBase} eight`} + ${hundredsStart + 9} | ${`${hundredsBase} nine`} + `('converts $value to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { + doExpect(value, numberName); + }); + }); + + describe(`${hundredsStart + 10}-${hundredsStart + 19}`, () => { + it.each` + value | numberName + ${hundredsStart + 10} | ${`${hundredsBase} ten`} + ${hundredsStart + 11} | ${`${hundredsBase} eleven`} + ${hundredsStart + 12} | ${`${hundredsBase} twelve`} + ${hundredsStart + 13} | ${`${hundredsBase} thirteen`} + ${hundredsStart + 14} | ${`${hundredsBase} fourteen`} + ${hundredsStart + 15} | ${`${hundredsBase} fifteen`} + ${hundredsStart + 16} | ${`${hundredsBase} sixteen`} + ${hundredsStart + 17} | ${`${hundredsBase} seventeen`} + ${hundredsStart + 18} | ${`${hundredsBase} eighteen`} + ${hundredsStart + 19} | ${`${hundredsBase} nineteen`} + `('converts $value to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { + doExpect(value, numberName); + }); + }); + + describe.each` + start | end | base + ${20} | ${29} | ${'twenty'} + ${30} | ${39} | ${'thirty'} + ${40} | ${49} | ${'forty'} + ${50} | ${59} | ${'fifty'} + ${60} | ${69} | ${'sixty'} + ${70} | ${79} | ${'seventy'} + ${80} | ${89} | ${'eighty'} + ${90} | ${99} | ${'ninety'} + `('$start-$end', ({ + start, base, + }: { start: number, base: string }) => { + it.each` + value | numberName + ${hundredsStart + start} | ${`${hundredsBase} ${base}`} + ${hundredsStart + start + 1} | ${`${hundredsBase} ${base}-one`} + ${hundredsStart + start + 2} | ${`${hundredsBase} ${base}-two`} + ${hundredsStart + start + 3} | ${`${hundredsBase} ${base}-three`} + ${hundredsStart + start + 4} | ${`${hundredsBase} ${base}-four`} + ${hundredsStart + start + 5} | ${`${hundredsBase} ${base}-five`} + ${hundredsStart + start + 6} | ${`${hundredsBase} ${base}-six`} + ${hundredsStart + start + 7} | ${`${hundredsBase} ${base}-seven`} + ${hundredsStart + start + 8} | ${`${hundredsBase} ${base}-eight`} + ${hundredsStart + start + 9} | ${`${hundredsBase} ${base}-nine`} + `('converts $value to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { + doExpect(value, numberName); + }); + }); + }); + }); + + it.each` + value | numberName + ${'1e+6'} | ${'one million'} + ${'1e+9'} | ${'one billion'} + ${'1e+12'} | ${'one trillion'} + ${'1e+15'} | ${'one quadrillion'} + ${'1e+18'} | ${'one quintillion'} + ${'1e+21'} | ${'one sextillion'} + ${'1e+24'} | ${'one septillion'} + ${'1e+27'} | ${'one octillion'} + ${'1e+30'} | ${'one nonillion'} + `('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { + doExpect(value, numberName); + }); + + it.each` + value | numberName + ${'1e+33'} | ${'one decillion'} + ${'1e+36'} | ${'one undecillion'} + ${'1e+39'} | ${'one duodecillion'} + ${'1e+42'} | ${'one tredecillion'} + ${'1e+45'} | ${'one quattuordecillion'} + ${'1e+48'} | ${'one quindecillion'} + ${'1e+51'} | ${'one sexdecillion'} + ${'1e+54'} | ${'one septendecillion'} + ${'1e+57'} | ${'one octodecillion'} + ${'1e+60'} | ${'one novemdecillion'} + `('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { + doExpect(value, numberName); + }); + + it.each` + value | numberName + ${'1e+63'} | ${'one vigintillion'} + ${'1e+66'} | ${'one unvigintillion'} + ${'1e+69'} | ${'one duovigintillion'} + ${'1e+72'} | ${'one trevigintillion'} + ${'1e+75'} | ${'one quattuorvigintillion'} + ${'1e+78'} | ${'one quinvigintillion'} + ${'1e+81'} | ${'one sexvigintillion'} + ${'1e+84'} | ${'one septenvigintillion'} + ${'1e+87'} | ${'one octovigintillion'} + ${'1e+90'} | ${'one novemvigintillion'} + `('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { + doExpect(value, numberName); + }); + + it.each` + value | numberName + ${'1e+93'} | ${'one trigintillion'} + ${'1e+123'} | ${'one quadragintillion'} + ${'1e+153'} | ${'one quinquagintillion'} + ${'1e+183'} | ${'one sexagintillion'} + ${'1e+213'} | ${'one septuagintillion'} + ${'1e+243'} | ${'one octogintillion'} + ${'1e+273'} | ${'one nonagintillion'} + `('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { + doExpect(value, numberName); + }); + + it.each` + value | numberName + ${'1e+303'} | ${'one centillion'} + ${'1e+306'} | ${'one cenuntillion'} + ${'1e+309'} | ${'one cenduotillion'} + ${'1e+312'} | ${'one centretillion'} + ${'1e+315'} | ${'one cenquattuortillion'} + ${'1e+318'} | ${'one cenquintillion'} + ${'1e+321'} | ${'one censextillion'} + ${'1e+324'} | ${'one censeptentillion'} + ${'1e+327'} | ${'one cenoctotillion'} + ${'1e+330'} | ${'one cennovemtillion'} + ${'1e+603'} | ${'one duocentillion'} + ${'1e+903'} | ${'one trecentillion'} + ${'1e+1203'} | ${'one quadringentillion'} + ${'1e+1503'} | ${'one quingentillion'} + ${'1e+1803'} | ${'one sescentillion'} + ${'1e+2103'} | ${'one septingentillion'} + ${'1e+2403'} | ${'one octingentillion'} + ${'1e+2703'} | ${'one nongentillion'} + `('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { + doExpect(value, numberName); + }); + + it('converts \'1e+3003\' to \'one milliatillion\'', () => { + doExpect('1e+3003', 'one milliatillion'); + }); +}); diff --git a/packages/core/test/systems/en-US.test.ts b/packages/core/test/systems/en-US.test.ts deleted file mode 100644 index 2b89bf0..0000000 --- a/packages/core/test/systems/en-US.test.ts +++ /dev/null @@ -1,304 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { parse, stringify, systems } from '../../src'; -import { numberToExponential } from '../../src/exponent'; - -const options = { - system: systems.enUS.shortCount, -}; - -const stringifyOptions = { - ...options, - stringifyGroupsOptions: { - addTensDashes: false, - }, -}; - -describe('numerica', () => { - describe('group names', () => { - describe('0-9', () => { - it.each` - ones | expected - ${0} | ${'zero'} - ${1} | ${'one'} - ${2} | ${'two'} - ${3} | ${'three'} - ${4} | ${'four'} - ${5} | ${'five'} - ${6} | ${'six'} - ${7} | ${'seven'} - ${8} | ${'eight'} - ${9} | ${'nine'} - `('converts $ones to $expected', ({ ones, expected }: { ones: number, expected: string }) => { - expect(stringify(ones, stringifyOptions)).toBe(expected); - expect(parse(expected, { ...options, type: 'number' })).toBe(ones); - }); - }); - - describe('10-19', () => { - it.each` - tenPlusOnes | expected - ${10} | ${'ten'} - ${11} | ${'eleven'} - ${12} | ${'twelve'} - ${13} | ${'thirteen'} - ${14} | ${'fourteen'} - ${15} | ${'fifteen'} - ${16} | ${'sixteen'} - ${17} | ${'seventeen'} - ${18} | ${'eighteen'} - ${19} | ${'nineteen'} - `('converts $tenPlusOnes to $expected', ({ tenPlusOnes, expected }: { tenPlusOnes: number, expected: string }) => { - expect(stringify(tenPlusOnes, stringifyOptions)).toBe(expected); - expect(parse(expected, { ...options, type: 'number' })).toBe(tenPlusOnes); - }); - }); - - describe.each` - tensStart | tensEnd | tensBase - ${20} | ${29} | ${'twenty'} - ${30} | ${39} | ${'thirty'} - ${40} | ${49} | ${'forty'} - ${50} | ${59} | ${'fifty'} - ${60} | ${69} | ${'sixty'} - ${70} | ${79} | ${'seventy'} - ${80} | ${89} | ${'eighty'} - ${90} | ${99} | ${'ninety'} - `('$tensStart-$tensEnd', ({ - tensStart, tensBase, - }: { tensStart: number, tensBase: string }) => { - it.each` - value | expected - ${tensStart} | ${tensBase} - ${tensStart + 1} | ${`${tensBase} one`} - ${tensStart + 2} | ${`${tensBase} two`} - ${tensStart + 3} | ${`${tensBase} three`} - ${tensStart + 4} | ${`${tensBase} four`} - ${tensStart + 5} | ${`${tensBase} five`} - ${tensStart + 6} | ${`${tensBase} six`} - ${tensStart + 7} | ${`${tensBase} seven`} - ${tensStart + 8} | ${`${tensBase} eight`} - ${tensStart + 9} | ${`${tensBase} nine`} - `('converts $value to $expected', ({ value, expected }: { value: number, expected: string }) => { - expect(stringify(value, stringifyOptions)).toBe(expected); - expect(parse(expected, { ...options, type: 'number' })).toBe(value); - }); - }); - - describe.each` - hundredsStart | hundredsEnd | hundredsBase - ${100} | ${199} | ${'one hundred'} - ${200} | ${299} | ${'two hundred'} - ${300} | ${399} | ${'three hundred'} - ${400} | ${499} | ${'four hundred'} - ${500} | ${599} | ${'five hundred'} - ${600} | ${699} | ${'six hundred'} - ${700} | ${799} | ${'seven hundred'} - ${800} | ${899} | ${'eight hundred'} - ${900} | ${999} | ${'nine hundred'} - `('$hundredsStart-$hundredsEnd', ({ - hundredsStart, hundredsBase, - }: { hundredsStart: number, hundredsBase: string }) => { - describe(`${hundredsStart}-${hundredsStart + 9}`, () => { - it.each` - value | expected - ${hundredsStart} | ${hundredsBase} - ${hundredsStart + 1} | ${`${hundredsBase} one`} - ${hundredsStart + 2} | ${`${hundredsBase} two`} - ${hundredsStart + 3} | ${`${hundredsBase} three`} - ${hundredsStart + 4} | ${`${hundredsBase} four`} - ${hundredsStart + 5} | ${`${hundredsBase} five`} - ${hundredsStart + 6} | ${`${hundredsBase} six`} - ${hundredsStart + 7} | ${`${hundredsBase} seven`} - ${hundredsStart + 8} | ${`${hundredsBase} eight`} - ${hundredsStart + 9} | ${`${hundredsBase} nine`} - `('converts $value to $expected', ({ value, expected }: { value: number, expected: string }) => { - expect(stringify(value, stringifyOptions)).toBe(expected); - expect(parse(expected, { ...options, type: 'number' })).toBe(value); - }); - }); - - describe(`${hundredsStart + 10}-${hundredsStart + 19}`, () => { - it.each` - value | expected - ${hundredsStart + 10} | ${`${hundredsBase} ten`} - ${hundredsStart + 11} | ${`${hundredsBase} eleven`} - ${hundredsStart + 12} | ${`${hundredsBase} twelve`} - ${hundredsStart + 13} | ${`${hundredsBase} thirteen`} - ${hundredsStart + 14} | ${`${hundredsBase} fourteen`} - ${hundredsStart + 15} | ${`${hundredsBase} fifteen`} - ${hundredsStart + 16} | ${`${hundredsBase} sixteen`} - ${hundredsStart + 17} | ${`${hundredsBase} seventeen`} - ${hundredsStart + 18} | ${`${hundredsBase} eighteen`} - ${hundredsStart + 19} | ${`${hundredsBase} nineteen`} - `('converts $value to $expected', ({ value, expected }: { value: number, expected: string }) => { - expect(stringify(value, stringifyOptions)).toBe(expected); - expect(parse(expected, { ...options, type: 'number' })).toBe(value); - }); - }); - - describe.each` - start | end | base - ${20} | ${29} | ${'twenty'} - ${30} | ${39} | ${'thirty'} - ${40} | ${49} | ${'forty'} - ${50} | ${59} | ${'fifty'} - ${60} | ${69} | ${'sixty'} - ${70} | ${79} | ${'seventy'} - ${80} | ${89} | ${'eighty'} - ${90} | ${99} | ${'ninety'} - `('$start-$end', ({ - start, base, - }: { start: number, base: string }) => { - it.each` - value | expected - ${hundredsStart + start} | ${`${hundredsBase} ${base}`} - ${hundredsStart + start + 1} | ${`${hundredsBase} ${base} one`} - ${hundredsStart + start + 2} | ${`${hundredsBase} ${base} two`} - ${hundredsStart + start + 3} | ${`${hundredsBase} ${base} three`} - ${hundredsStart + start + 4} | ${`${hundredsBase} ${base} four`} - ${hundredsStart + start + 5} | ${`${hundredsBase} ${base} five`} - ${hundredsStart + start + 6} | ${`${hundredsBase} ${base} six`} - ${hundredsStart + start + 7} | ${`${hundredsBase} ${base} seven`} - ${hundredsStart + start + 8} | ${`${hundredsBase} ${base} eight`} - ${hundredsStart + start + 9} | ${`${hundredsBase} ${base} nine`} - `('converts $value to $expected', ({ value, expected }: { value: number, expected: string }) => { - expect(stringify(value, stringifyOptions)).toBe(expected); - expect(parse(expected, { ...options, type: 'number' })).toBe(value); - }); - }); - }); - }); - - it('converts 1000 to one thousand', () => { - expect(stringify(1000, stringifyOptions)).toBe('one thousand'); - expect(parse('one thousand', { ...options, type: 'number' })).toBe(1000); - }); - - it('converts 10000 to ten thousand', () => { - expect(stringify(10000, stringifyOptions)).toBe('ten thousand'); - expect(parse('ten thousand', { ...options, type: 'number' })).toBe(10000); - }); - - it('converts 100000 to one hundred thousand', () => { - expect(stringify(100000, stringifyOptions)).toBe('one hundred thousand'); - expect(parse('one hundred thousand', { ...options, type: 'number' })).toBe(100000); - }); - - it('converts 123456 to one hundred twenty three thousand four hundred fifty six', () => { - expect(stringify(123456, stringifyOptions)).toBe('one hundred twenty three thousand four hundred fifty six'); - expect(parse('one hundred twenty three thousand four hundred fifty six', { ...options, type: 'number' })).toBe(123456); - }); - - it.each` - value | expected - ${1e+6} | ${'one million'} - ${1e+9} | ${'one billion'} - ${1e+12} | ${'one trillion'} - ${1e+15} | ${'one quadrillion'} - ${1e+18} | ${'one quintillion'} - ${1e+21} | ${'one sextillion'} - ${1e+24} | ${'one septillion'} - ${1e+27} | ${'one octillion'} - ${1e+30} | ${'one nonillion'} - `('converts $value to $expected', ({ value, expected }: { value: number, expected: string }) => { - expect(stringify(value, stringifyOptions)).toBe(expected); - expect(parse(expected, { ...options, type: 'number' })).toBe(value); - }); - - it.each` - value | expected - ${'1e+33'} | ${'one decillion'} - ${'1e+36'} | ${'one undecillion'} - ${'1e+39'} | ${'one duodecillion'} - ${'1e+42'} | ${'one tredecillion'} - ${'1e+45'} | ${'one quattuordecillion'} - ${'1e+48'} | ${'one quindecillion'} - ${'1e+51'} | ${'one sexdecillion'} - ${'1e+54'} | ${'one septendecillion'} - ${'1e+57'} | ${'one octodecillion'} - ${'1e+60'} | ${'one novemdecillion'} - `('converts $value to $expected', ({ value, expected }: { value: string, expected: string }) => { - expect(stringify(value, stringifyOptions)).toBe(expected); - expect(parse(expected, options)).toBe(value); - }); - - it.each` - value | expected - ${'1e+63'} | ${'one vigintillion'} - ${'1e+66'} | ${'one unvigintillion'} - ${'1e+69'} | ${'one duovigintillion'} - ${'1e+72'} | ${'one trevigintillion'} - ${'1e+75'} | ${'one quattuorvigintillion'} - ${'1e+78'} | ${'one quinvigintillion'} - ${'1e+81'} | ${'one sexvigintillion'} - ${'1e+84'} | ${'one septenvigintillion'} - ${'1e+87'} | ${'one octovigintillion'} - ${'1e+90'} | ${'one novemvigintillion'} - `('converts $value to $expected', ({ value, expected }: { value: string, expected: string }) => { - expect(stringify(value, stringifyOptions)).toBe(expected); - expect(parse(expected, options)).toBe(value); - }); - - it.each` - value | expected - ${'1e+93'} | ${'one trigintillion'} - ${'1e+123'} | ${'one quadragintillion'} - ${'1e+153'} | ${'one quinquagintillion'} - ${'1e+183'} | ${'one sexagintillion'} - ${'1e+213'} | ${'one septuagintillion'} - ${'1e+243'} | ${'one octogintillion'} - ${'1e+273'} | ${'one nonagintillion'} - `('converts $value to $expected', ({ value, expected }: { value: string, expected: string }) => { - expect(stringify(value, stringifyOptions)).toBe(expected); - expect(parse(expected, options)).toBe(value); - }); - - it('converts values', () => { - const exp1 = numberToExponential('123,456,789,012,345,678,901,234,567,890'); - // - // one hundred twenty three octillion - // four hundred fifty six septillion - // seven hundred eighty nine sextillion - // twelve quintillion - // three hundred forty five quadrillion - // six hundred seventy eight trillion - // nine hundred one billion - // two hundred thirty four million - // five hundred sixty seven thousand - // eight hundred ninety - expect(parse(stringify('123456789012345678901234567890'))).toBe(exp1); - - const value2 = '1000005000000'; - const exp2 = numberToExponential(value2); - expect(stringify(value2)).toBe('one trillion five million'); - expect(parse(stringify(value2))).toBe(exp2); - - const value3 = '12012'; - const exp3 = numberToExponential(value3); - expect(stringify(value3)).toBe('twelve thousand twelve'); - expect(parse(stringify(value3))).toBe(exp3); - - const value4 = '12020'; - const exp4 = numberToExponential(value4); - expect(stringify(value4)).toBe('twelve thousand twenty'); - expect(parse(stringify(value4))).toBe(exp4); - - const value5 = '1e3006'; - expect(stringify(value5)).toBe('one milliauntillion'); - }); - - it('converts short millia', () => { - const shortMillia1 = 'one millia^1-tillion'; - expect(parse(shortMillia1)).toBe('1e+3003'); - - const shortMillia2 = 'one millia^2-tillion'; - expect(parse(shortMillia2)).toBe('1e+3000003'); - expect(stringify(parse(shortMillia2), { stringifyGroupsOptions: { shortenMillia: true } })) - .toBe(shortMillia2); - - const exp = '1e+3003003'; - const shortMillia3 = 'one millia^2-unmilliatillion'; - expect(stringify(exp, { stringifyGroupsOptions: { shortenMillia: true } })).toBe(shortMillia3); - expect(parse(shortMillia3)).toBe('1e+3003003'); - }); -}); diff --git a/packages/core/test/systems/en-US/short-count.test.ts b/packages/core/test/systems/en-US/short-count.test.ts new file mode 100644 index 0000000..641769e --- /dev/null +++ b/packages/core/test/systems/en-US/short-count.test.ts @@ -0,0 +1,335 @@ +import { describe, it, expect } from 'vitest'; +import { + AllowedValue, + parse, + stringify, + systems, +} from '../../../src'; + +const options = { + system: systems.enUS.shortCount, +}; + +const doExpect = ( + value: AllowedValue, + stringified: string, + parsedValue: AllowedValue = value, +) => { + const stringifyOptions = { + ...options, + }; + + const parseOptions = { + ...options, + type: typeof value as ('number' | 'string' | 'bigint'), + }; + + expect(stringify(value, stringifyOptions)).toBe(stringified); + expect(parse(stringified, parseOptions)).toBe(parsedValue); +}; + +describe('American short count', () => { + describe('individual cases', () => { + const longNumberValue1 = '123,456,789,012,345,678,901,234,567,890'; + const longNumberStringified1 = [ + 'one hundred twenty-three octillion', + 'four hundred fifty-six septillion', + 'seven hundred eighty-nine sextillion', + 'twelve quintillion', + 'three hundred forty-five quadrillion', + 'six hundred seventy-eight trillion', + 'nine hundred one billion', + 'two hundred thirty-four million', + 'five hundred sixty-seven thousand', + 'eight hundred ninety', + ].join(' '); + + const longNumberParsedValue = '1.2345678901234567890123456789e+29'; + + it.each` + value | stringified | parsedValue + ${1000} | ${'one thousand'} | ${1000} + ${10000} | ${'ten thousand'} | ${10000} + ${12012} | ${'twelve thousand twelve'} | ${12012} + ${12020} | ${'twelve thousand twenty'} | ${12020} + ${20000} | ${'twenty thousand'} | ${20000} + ${100000} | ${'one hundred thousand'} | ${100000} + ${123456} | ${'one hundred twenty-three thousand four hundred fifty-six'} | ${123456} + ${'1000005000000'} | ${'one trillion five million'} | ${'1.000005e+12'} + ${longNumberValue1} | ${longNumberStringified1} | ${longNumberParsedValue} + ${'1e3006'} | ${'one milliauntillion'} | ${'1e+3006'} + `('converts $value to $stringified', ({ + value, + stringified, + parsedValue, + }: { value: AllowedValue, stringified: string, parsedValue: AllowedValue }) => { + doExpect(value, stringified, parsedValue); + }); + + it('converts one millia^1-tillion to 1e+3003', () => { + expect(parse('one millia^1-tillion')).toBe('1e+3003'); + }); + }); + + describe('blanket cases', () => { + it('converts one millia^2-tillion', () => { + const value = '1e+3000003'; + const stringified = 'one millia^2-tillion'; + expect(parse(stringified)).toBe(value); + expect(stringify(value, { stringifyGroupsOptions: { shortenMillia: true } })) + .toBe(stringified); + }); + + it('converts one millia^2-unmilliatillion', () => { + const value = '1e+3003003'; + const stringified = 'one millia^2-unmilliatillion'; + expect(stringify(value, { stringifyGroupsOptions: { shortenMillia: true } })) + .toBe(stringified); + expect(parse(stringified)).toBe(value); + }); + + it('converts one milliamilliaunmilliatillion', () => { + const value = '1e+3003003'; + const stringified = 'one milliamilliaunmilliatillion'; + expect(stringify(value)).toBe(stringified); + expect(parse(stringified)).toBe('1e+3003003'); + }); + }); + + describe('general', () => { + describe('0-9', () => { + it.each` + value | numberName + ${0} | ${'zero'} + ${1} | ${'one'} + ${2} | ${'two'} + ${3} | ${'three'} + ${4} | ${'four'} + ${5} | ${'five'} + ${6} | ${'six'} + ${7} | ${'seven'} + ${8} | ${'eight'} + ${9} | ${'nine'} + `('converts $ones to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { + doExpect(value, numberName); + }); + }); + + describe('10-19', () => { + it.each` + value | numberName + ${10} | ${'ten'} + ${11} | ${'eleven'} + ${12} | ${'twelve'} + ${13} | ${'thirteen'} + ${14} | ${'fourteen'} + ${15} | ${'fifteen'} + ${16} | ${'sixteen'} + ${17} | ${'seventeen'} + ${18} | ${'eighteen'} + ${19} | ${'nineteen'} + `('converts $tenPlusOnes to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { + doExpect(value, numberName); + }); + }); + + describe.each` + tensStart | tensEnd | tensBase + ${20} | ${29} | ${'twenty'} + ${30} | ${39} | ${'thirty'} + ${40} | ${49} | ${'forty'} + ${50} | ${59} | ${'fifty'} + ${60} | ${69} | ${'sixty'} + ${70} | ${79} | ${'seventy'} + ${80} | ${89} | ${'eighty'} + ${90} | ${99} | ${'ninety'} + `('$tensStart-$tensEnd', ({ + tensStart, tensBase, + }: { tensStart: number, tensBase: string }) => { + it.each` + value | numberName + ${tensStart} | ${tensBase} + ${tensStart + 1} | ${`${tensBase}-one`} + ${tensStart + 2} | ${`${tensBase}-two`} + ${tensStart + 3} | ${`${tensBase}-three`} + ${tensStart + 4} | ${`${tensBase}-four`} + ${tensStart + 5} | ${`${tensBase}-five`} + ${tensStart + 6} | ${`${tensBase}-six`} + ${tensStart + 7} | ${`${tensBase}-seven`} + ${tensStart + 8} | ${`${tensBase}-eight`} + ${tensStart + 9} | ${`${tensBase}-nine`} + `('converts $value to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { + doExpect(value, numberName); + }); + }); + + describe.each` + hundredsStart | hundredsEnd | hundredsBase + ${100} | ${199} | ${'one hundred'} + ${200} | ${299} | ${'two hundred'} + ${300} | ${399} | ${'three hundred'} + ${400} | ${499} | ${'four hundred'} + ${500} | ${599} | ${'five hundred'} + ${600} | ${699} | ${'six hundred'} + ${700} | ${799} | ${'seven hundred'} + ${800} | ${899} | ${'eight hundred'} + ${900} | ${999} | ${'nine hundred'} + `('$hundredsStart-$hundredsEnd', ({ + hundredsStart, hundredsBase, + }: { hundredsStart: number, hundredsBase: string }) => { + describe(`${hundredsStart}-${hundredsStart + 9}`, () => { + it.each` + value | numberName + ${hundredsStart} | ${hundredsBase} + ${hundredsStart + 1} | ${`${hundredsBase} one`} + ${hundredsStart + 2} | ${`${hundredsBase} two`} + ${hundredsStart + 3} | ${`${hundredsBase} three`} + ${hundredsStart + 4} | ${`${hundredsBase} four`} + ${hundredsStart + 5} | ${`${hundredsBase} five`} + ${hundredsStart + 6} | ${`${hundredsBase} six`} + ${hundredsStart + 7} | ${`${hundredsBase} seven`} + ${hundredsStart + 8} | ${`${hundredsBase} eight`} + ${hundredsStart + 9} | ${`${hundredsBase} nine`} + `('converts $value to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { + doExpect(value, numberName); + }); + }); + + describe(`${hundredsStart + 10}-${hundredsStart + 19}`, () => { + it.each` + value | numberName + ${hundredsStart + 10} | ${`${hundredsBase} ten`} + ${hundredsStart + 11} | ${`${hundredsBase} eleven`} + ${hundredsStart + 12} | ${`${hundredsBase} twelve`} + ${hundredsStart + 13} | ${`${hundredsBase} thirteen`} + ${hundredsStart + 14} | ${`${hundredsBase} fourteen`} + ${hundredsStart + 15} | ${`${hundredsBase} fifteen`} + ${hundredsStart + 16} | ${`${hundredsBase} sixteen`} + ${hundredsStart + 17} | ${`${hundredsBase} seventeen`} + ${hundredsStart + 18} | ${`${hundredsBase} eighteen`} + ${hundredsStart + 19} | ${`${hundredsBase} nineteen`} + `('converts $value to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { + doExpect(value, numberName); + }); + }); + + describe.each` + start | end | base + ${20} | ${29} | ${'twenty'} + ${30} | ${39} | ${'thirty'} + ${40} | ${49} | ${'forty'} + ${50} | ${59} | ${'fifty'} + ${60} | ${69} | ${'sixty'} + ${70} | ${79} | ${'seventy'} + ${80} | ${89} | ${'eighty'} + ${90} | ${99} | ${'ninety'} + `('$start-$end', ({ + start, base, + }: { start: number, base: string }) => { + it.each` + value | numberName + ${hundredsStart + start} | ${`${hundredsBase} ${base}`} + ${hundredsStart + start + 1} | ${`${hundredsBase} ${base}-one`} + ${hundredsStart + start + 2} | ${`${hundredsBase} ${base}-two`} + ${hundredsStart + start + 3} | ${`${hundredsBase} ${base}-three`} + ${hundredsStart + start + 4} | ${`${hundredsBase} ${base}-four`} + ${hundredsStart + start + 5} | ${`${hundredsBase} ${base}-five`} + ${hundredsStart + start + 6} | ${`${hundredsBase} ${base}-six`} + ${hundredsStart + start + 7} | ${`${hundredsBase} ${base}-seven`} + ${hundredsStart + start + 8} | ${`${hundredsBase} ${base}-eight`} + ${hundredsStart + start + 9} | ${`${hundredsBase} ${base}-nine`} + `('converts $value to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { + doExpect(value, numberName); + }); + }); + }); + }); + + it.each` + value | numberName + ${'1e+6'} | ${'one million'} + ${'1e+9'} | ${'one billion'} + ${'1e+12'} | ${'one trillion'} + ${'1e+15'} | ${'one quadrillion'} + ${'1e+18'} | ${'one quintillion'} + ${'1e+21'} | ${'one sextillion'} + ${'1e+24'} | ${'one septillion'} + ${'1e+27'} | ${'one octillion'} + ${'1e+30'} | ${'one nonillion'} + `('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { + doExpect(value, numberName); + }); + + it.each` + value | numberName + ${'1e+33'} | ${'one decillion'} + ${'1e+36'} | ${'one undecillion'} + ${'1e+39'} | ${'one duodecillion'} + ${'1e+42'} | ${'one tredecillion'} + ${'1e+45'} | ${'one quattuordecillion'} + ${'1e+48'} | ${'one quindecillion'} + ${'1e+51'} | ${'one sexdecillion'} + ${'1e+54'} | ${'one septendecillion'} + ${'1e+57'} | ${'one octodecillion'} + ${'1e+60'} | ${'one novemdecillion'} + `('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { + doExpect(value, numberName); + }); + + it.each` + value | numberName + ${'1e+63'} | ${'one vigintillion'} + ${'1e+66'} | ${'one unvigintillion'} + ${'1e+69'} | ${'one duovigintillion'} + ${'1e+72'} | ${'one trevigintillion'} + ${'1e+75'} | ${'one quattuorvigintillion'} + ${'1e+78'} | ${'one quinvigintillion'} + ${'1e+81'} | ${'one sexvigintillion'} + ${'1e+84'} | ${'one septenvigintillion'} + ${'1e+87'} | ${'one octovigintillion'} + ${'1e+90'} | ${'one novemvigintillion'} + `('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { + doExpect(value, numberName); + }); + + it.each` + value | numberName + ${'1e+93'} | ${'one trigintillion'} + ${'1e+123'} | ${'one quadragintillion'} + ${'1e+153'} | ${'one quinquagintillion'} + ${'1e+183'} | ${'one sexagintillion'} + ${'1e+213'} | ${'one septuagintillion'} + ${'1e+243'} | ${'one octogintillion'} + ${'1e+273'} | ${'one nonagintillion'} + `('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { + doExpect(value, numberName); + }); + + it.each` + value | numberName + ${'1e+303'} | ${'one centillion'} + ${'1e+306'} | ${'one cenuntillion'} + ${'1e+309'} | ${'one cenduotillion'} + ${'1e+312'} | ${'one centretillion'} + ${'1e+315'} | ${'one cenquattuortillion'} + ${'1e+318'} | ${'one cenquintillion'} + ${'1e+321'} | ${'one censextillion'} + ${'1e+324'} | ${'one censeptentillion'} + ${'1e+327'} | ${'one cenoctotillion'} + ${'1e+330'} | ${'one cennovemtillion'} + ${'1e+603'} | ${'one duocentillion'} + ${'1e+903'} | ${'one trecentillion'} + ${'1e+1203'} | ${'one quadringentillion'} + ${'1e+1503'} | ${'one quingentillion'} + ${'1e+1803'} | ${'one sescentillion'} + ${'1e+2103'} | ${'one septingentillion'} + ${'1e+2403'} | ${'one octingentillion'} + ${'1e+2703'} | ${'one nongentillion'} + `('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { + doExpect(value, numberName); + }); + + it('converts \'1e+3003\' to \'one milliatillion\'', () => { + doExpect('1e+3003', 'one milliatillion'); + }); +});