@@ -108,3 +108,23 @@ export class InvalidTokenError extends Error { | |||||
super(`Invalid token: ${token}`); | 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, | |||||
); |
@@ -15,7 +15,7 @@ const EXPONENT_DELIMITER = 'e' as const; | |||||
/** | /** | ||||
* Allowed value type for {@link stringify}. | * Allowed value type for {@link stringify}. | ||||
*/ | */ | ||||
type AllowedValue = string | number | bigint; | |||||
export type AllowedValue = string | number | bigint; | |||||
/** | /** | ||||
* Array of allowed types for {@link parse}. | * Array of allowed types for {@link parse}. | ||||
@@ -1 +1,2 @@ | |||||
export * as shortCount from '../en-US/short-count'; | export * as shortCount from '../en-US/short-count'; | ||||
export * as longCount from './long-count'; |
@@ -0,0 +1,2 @@ | |||||
export * from './parse'; | |||||
export * from './stringify'; |
@@ -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<ParserState>( | |||||
(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}`; | |||||
}; |
@@ -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<TensName, 'zero' | 'ten'>}${addTensDashes ? 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. | |||||
* @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<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], _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<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]]; | |||||
}, | |||||
[], | |||||
); | |||||
}; | |||||
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}`; | |||||
}; |
@@ -1,4 +1,5 @@ | |||||
import { | import { | ||||
bigIntMax, bigIntMin, | |||||
Group, | Group, | ||||
GROUP_DIGITS_INDEX, | GROUP_DIGITS_INDEX, | ||||
GROUP_PLACE_INDEX, | GROUP_PLACE_INDEX, | ||||
@@ -29,7 +30,7 @@ import { | |||||
TENS_ONES_SEPARATOR, | TENS_ONES_SEPARATOR, | ||||
TensName, | TensName, | ||||
THOUSAND, | THOUSAND, | ||||
} from '../common'; | |||||
} from '../../en/common'; | |||||
const FINAL_TOKEN = '' as const; | const FINAL_TOKEN = '' as const; | ||||
@@ -438,26 +439,6 @@ export const parseGroups = (tokens: string[]) => { | |||||
return groups; | 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. | * Combines groups into a string. | ||||
* @param groups - The groups to combine. | * @param groups - The groups to combine. | ||||
@@ -31,7 +31,7 @@ import { | |||||
TENS_ONES_SEPARATOR, | TENS_ONES_SEPARATOR, | ||||
TensName, | TensName, | ||||
THOUSAND, | THOUSAND, | ||||
} from '../common'; | |||||
} from '../../en/common'; | |||||
/** | /** | ||||
* Builds a name for numbers in tens and ones. | * 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. | * Builds a name for numbers in the millions. | ||||
* @param millions - Millions digit. | * @param millions - Millions digit. | ||||
* @param currentMillia - Current millia- group. | |||||
* @param longestMilliaCount - Number of millia- groups. | * @param longestMilliaCount - Number of millia- groups. | ||||
* @returns string The millions prefix. | * @returns string The millions prefix. | ||||
*/ | */ | ||||
@@ -98,6 +99,7 @@ const makeMillionsPrefix = ( | |||||
* Builds a name for numbers in the decillions. | * Builds a name for numbers in the decillions. | ||||
* @param decillions - Decillions digit. | * @param decillions - Decillions digit. | ||||
* @param millions - Millions digit. | * @param millions - Millions digit. | ||||
* @param currentMillia - Current millia- group. | |||||
* @param longestMilliaCount - Number of millia- groups. | * @param longestMilliaCount - Number of millia- groups. | ||||
* @returns string The decillions prefix. | * @returns string The decillions prefix. | ||||
*/ | */ | ||||
@@ -121,6 +123,7 @@ const makeDecillionsPrefix = ( | |||||
* @param centillions - Centillions digit. | * @param centillions - Centillions digit. | ||||
* @param decillions - Decillions digit. | * @param decillions - Decillions digit. | ||||
* @param millions - Millions digit. | * @param millions - Millions digit. | ||||
* @param currentMillia - Current millia- group. | |||||
* @param longestMilliaCount - Number of millia- groups. | * @param longestMilliaCount - Number of millia- groups. | ||||
* @returns string The centillions prefix. | * @returns string The centillions prefix. | ||||
*/ | */ | ||||
@@ -292,6 +295,7 @@ export const splitIntoGroups = (value: string): Group[] => { | |||||
}, | }, | ||||
) | ) | ||||
.split(EXPONENT_DELIMITER); | .split(EXPONENT_DELIMITER); | ||||
// FIXME use bigint for exponent and indexing??? | |||||
const exponent = Number(exponentString); | const exponent = Number(exponentString); | ||||
const significantDigits = significand.replace(new RegExp(`\\${DECIMAL_POINT}`, 'g'), ''); | const significantDigits = significand.replace(new RegExp(`\\${DECIMAL_POINT}`, 'g'), ''); | ||||
return significantDigits.split('').reduce<Group[]>( | return significantDigits.split('').reduce<Group[]>( | ||||
@@ -1,6 +1,6 @@ | |||||
import { describe, it, expect } from 'vitest'; | 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 = { | const stringifyOptions = { | ||||
stringifyGroupsOptions: { | stringifyGroupsOptions: { | ||||
@@ -11,17 +11,17 @@ const stringifyOptions = { | |||||
describe('Landon\'s original test cases', () => { | describe('Landon\'s original test cases', () => { | ||||
describe('Basic conversions', () => { | describe('Basic conversions', () => { | ||||
it.each` | 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( | it( | ||||
@@ -85,86 +85,86 @@ describe('Landon\'s original test cases', () => { | |||||
describe('Medium size numbers (<= 1e+63)', () => { | describe('Medium size numbers (<= 1e+63)', () => { | ||||
describe('Table 1', () => { | describe('Table 1', () => { | ||||
it.each` | 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)', () => { | describe('Large size numbers (< 1e+303)', () => { | ||||
it.each` | 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)', () => { | describe('Gigantic size numbers (< 1e+3003)', () => { | ||||
it.each` | 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` | 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+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'} | ${'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'} | ${'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+1174743648579'} | ${'one trecenunnonaginmilliamilliamilliaquingenunoctoginmilliamilliaduocensexdecmilliacenduononagintillion'} | ||||
${'1e+3000000000003'} | ${'one milliamilliamilliamilliatillion'} | ${'1e+3000000000003'} | ${'one milliamilliamilliamilliatillion'} | ||||
${'1e+696276510359811'} | ${'one duocenduotriginmilliamilliamilliamilliaduononaginmilliamilliamilliacenseptuaginmilliamilliacennovemdecmillianongensextrigintillion'} | ${'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)); | |||||
}); | }); | ||||
}); | }); | ||||
}); | }); |
@@ -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'); | |||||
}); | |||||
}); |
@@ -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'); | |||||
}); | |||||
}); |
@@ -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'); | |||||
}); | |||||
}); |
@@ -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'); | |||||
}); | |||||
}); |