@@ -108,3 +108,23 @@ export class InvalidTokenError extends Error { | |||
super(`Invalid token: ${token}`); | |||
} | |||
} | |||
export const bigIntMax = (...b: bigint[]) => b.reduce( | |||
(previousMax, current) => { | |||
if (typeof previousMax === 'undefined') { | |||
return current; | |||
} | |||
return previousMax > current ? previousMax : current; | |||
}, | |||
undefined as bigint | undefined, | |||
); | |||
export const bigIntMin = (...b: bigint[]) => b.reduce( | |||
(previousMin, current) => { | |||
if (typeof previousMin === 'undefined') { | |||
return current; | |||
} | |||
return previousMin < current ? previousMin : current; | |||
}, | |||
undefined as bigint | undefined, | |||
); |
@@ -15,7 +15,7 @@ const EXPONENT_DELIMITER = 'e' as const; | |||
/** | |||
* Allowed value type for {@link stringify}. | |||
*/ | |||
type AllowedValue = string | number | bigint; | |||
export type AllowedValue = string | number | bigint; | |||
/** | |||
* Array of allowed types for {@link parse}. | |||
@@ -1 +1,2 @@ | |||
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 { | |||
bigIntMax, bigIntMin, | |||
Group, | |||
GROUP_DIGITS_INDEX, | |||
GROUP_PLACE_INDEX, | |||
@@ -29,7 +30,7 @@ import { | |||
TENS_ONES_SEPARATOR, | |||
TensName, | |||
THOUSAND, | |||
} from '../common'; | |||
} from '../../en/common'; | |||
const FINAL_TOKEN = '' as const; | |||
@@ -438,26 +439,6 @@ export const parseGroups = (tokens: string[]) => { | |||
return groups; | |||
}; | |||
const bigIntMax = (...b: bigint[]) => b.reduce( | |||
(previousMax, current) => { | |||
if (typeof previousMax === 'undefined') { | |||
return current; | |||
} | |||
return previousMax > current ? previousMax : current; | |||
}, | |||
undefined as bigint | undefined, | |||
); | |||
const bigIntMin = (...b: bigint[]) => b.reduce( | |||
(previousMin, current) => { | |||
if (typeof previousMin === 'undefined') { | |||
return current; | |||
} | |||
return previousMin < current ? previousMin : current; | |||
}, | |||
undefined as bigint | undefined, | |||
); | |||
/** | |||
* Combines groups into a string. | |||
* @param groups - The groups to combine. | |||
@@ -31,7 +31,7 @@ import { | |||
TENS_ONES_SEPARATOR, | |||
TensName, | |||
THOUSAND, | |||
} from '../common'; | |||
} from '../../en/common'; | |||
/** | |||
* Builds a name for numbers in tens and ones. | |||
@@ -79,6 +79,7 @@ const makeHundredsName = (hundreds: number, tens: number, ones: number, addTensD | |||
/** | |||
* Builds a name for numbers in the millions. | |||
* @param millions - Millions digit. | |||
* @param currentMillia - Current millia- group. | |||
* @param longestMilliaCount - Number of millia- groups. | |||
* @returns string The millions prefix. | |||
*/ | |||
@@ -98,6 +99,7 @@ const makeMillionsPrefix = ( | |||
* Builds a name for numbers in the decillions. | |||
* @param decillions - Decillions digit. | |||
* @param millions - Millions digit. | |||
* @param currentMillia - Current millia- group. | |||
* @param longestMilliaCount - Number of millia- groups. | |||
* @returns string The decillions prefix. | |||
*/ | |||
@@ -121,6 +123,7 @@ const makeDecillionsPrefix = ( | |||
* @param centillions - Centillions digit. | |||
* @param decillions - Decillions digit. | |||
* @param millions - Millions digit. | |||
* @param currentMillia - Current millia- group. | |||
* @param longestMilliaCount - Number of millia- groups. | |||
* @returns string The centillions prefix. | |||
*/ | |||
@@ -292,6 +295,7 @@ export const splitIntoGroups = (value: string): Group[] => { | |||
}, | |||
) | |||
.split(EXPONENT_DELIMITER); | |||
// FIXME use bigint for exponent and indexing??? | |||
const exponent = Number(exponentString); | |||
const significantDigits = significand.replace(new RegExp(`\\${DECIMAL_POINT}`, 'g'), ''); | |||
return significantDigits.split('').reduce<Group[]>( | |||
@@ -1,6 +1,6 @@ | |||
import { describe, it, expect } from 'vitest'; | |||
import { stringify, parse } from '../../../src'; | |||
import { numberToExponential } from '../../../src/exponent'; | |||
import { stringify, parse } from '../src'; | |||
import { numberToExponential } from '../src/exponent'; | |||
const stringifyOptions = { | |||
stringifyGroupsOptions: { | |||
@@ -11,17 +11,17 @@ const stringifyOptions = { | |||
describe('Landon\'s original test cases', () => { | |||
describe('Basic conversions', () => { | |||
it.each` | |||
value | americanName | |||
${1} | ${'one'} | |||
${1000} | ${'one thousand'} | |||
${1000000} | ${'one million'} | |||
${1000000000} | ${'one billion'} | |||
${1000000000000} | ${'one trillion'} | |||
${1000000000000000} | ${'one quadrillion'} | |||
${1000000000000000000} | ${'one quintillion'} | |||
`('converts $value to $americanName', ({ value, americanName }: { value: number, americanName: string }) => { | |||
expect(stringify(value, stringifyOptions)).toBe(americanName); | |||
expect(parse(americanName, { type: 'number' })).toBe(value); | |||
value | numberName | |||
${'1e+0'} | ${'one'} | |||
${'1e+3'} | ${'one thousand'} | |||
${'1e+6'} | ${'one million'} | |||
${'1e+9'} | ${'one billion'} | |||
${'1e+12'} | ${'one trillion'} | |||
${'1e+15'} | ${'one quadrillion'} | |||
${'1e+18'} | ${'one quintillion'} | |||
`('converts $value to $numberName', ({ value, numberName }: { value: number, numberName: string }) => { | |||
expect(stringify(value, stringifyOptions)).toBe(numberName); | |||
expect(parse(numberName)).toBe(value); | |||
}); | |||
it( | |||
@@ -85,86 +85,86 @@ describe('Landon\'s original test cases', () => { | |||
describe('Medium size numbers (<= 1e+63)', () => { | |||
describe('Table 1', () => { | |||
it.each` | |||
value | americanName | |||
${'1e+9'} | ${'billion'} | |||
${'1e+12'} | ${'trillion'} | |||
${'1e+15'} | ${'quadrillion'} | |||
${'1e+18'} | ${'quintillion'} | |||
${'1e+21'} | ${'sextillion'} | |||
${'1e+24'} | ${'septillion'} | |||
${'1e+27'} | ${'octillion'} | |||
${'1e+30'} | ${'nonillion'} | |||
${'1e+33'} | ${'decillion'} | |||
${'1e+36'} | ${'undecillion'} | |||
${'1e+39'} | ${'duodecillion'} | |||
${'1e+42'} | ${'tredecillion'} | |||
${'1e+45'} | ${'quattuordecillion'} | |||
${'1e+48'} | ${'quindecillion'} | |||
${'1e+51'} | ${'sexdecillion'} | |||
${'1e+54'} | ${'septendecillion'} | |||
${'1e+57'} | ${'octodecillion'} | |||
${'1e+60'} | ${'novemdecillion'} | |||
${'1e+63'} | ${'vigintillion'} | |||
`('converts $value to $americanName', ({ value, americanName }: { value: string, americanName: string }) => { | |||
expect(stringify(value, stringifyOptions)).toBe(`one ${americanName}`); | |||
expect(parse(`one ${americanName}`)).toBe(value); | |||
value | numberName | |||
${'1e+9'} | ${'one billion'} | |||
${'1e+12'} | ${'one trillion'} | |||
${'1e+15'} | ${'one quadrillion'} | |||
${'1e+18'} | ${'one quintillion'} | |||
${'1e+21'} | ${'one sextillion'} | |||
${'1e+24'} | ${'one septillion'} | |||
${'1e+27'} | ${'one octillion'} | |||
${'1e+30'} | ${'one nonillion'} | |||
${'1e+33'} | ${'one decillion'} | |||
${'1e+36'} | ${'one undecillion'} | |||
${'1e+39'} | ${'one duodecillion'} | |||
${'1e+42'} | ${'one tredecillion'} | |||
${'1e+45'} | ${'one quattuordecillion'} | |||
${'1e+48'} | ${'one quindecillion'} | |||
${'1e+51'} | ${'one sexdecillion'} | |||
${'1e+54'} | ${'one septendecillion'} | |||
${'1e+57'} | ${'one octodecillion'} | |||
${'1e+60'} | ${'one novemdecillion'} | |||
${'1e+63'} | ${'one vigintillion'} | |||
`('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { | |||
expect(stringify(value, stringifyOptions)).toBe(numberName); | |||
expect(parse(numberName)).toBe(value); | |||
}); | |||
}); | |||
}); | |||
describe('Large size numbers (< 1e+303)', () => { | |||
it.each` | |||
value | americanName | |||
${'1e+66'} | ${'unvigintillion'} | |||
${'1e+69'} | ${'duovigintillion'} | |||
${'1e+72'} | ${'trevigintillion'} | |||
${'1e+75'} | ${'quattuorvigintillion'} | |||
${'1e+78'} | ${'quinvigintillion'} | |||
${'1e+81'} | ${'sexvigintillion'} | |||
${'1e+84'} | ${'septenvigintillion'} | |||
${'1e+87'} | ${'octovigintillion'} | |||
${'1e+90'} | ${'novemvigintillion'} | |||
${'1e+93'} | ${'trigintillion'} | |||
${'1e+123'} | ${'quadragintillion'} | |||
${'1e+150'} | ${'novemquadragintillion'} | |||
${'1e+153'} | ${'quinquagintillion'} | |||
${'1e+156'} | ${'unquinquagintillion'} | |||
${'1e+183'} | ${'sexagintillion'} | |||
${'1e+213'} | ${'septuagintillion'} | |||
${'1e+222'} | ${'treseptuagintillion'} | |||
${'1e+243'} | ${'octogintillion'} | |||
${'1e+273'} | ${'nonagintillion'} | |||
${'1e+300'} | ${'novemnonagintillion'} | |||
`('converts $value to $americanName', ({ value, americanName }: { value: string, americanName: string }) => { | |||
expect(stringify(value, stringifyOptions)).toBe(`one ${americanName}`); | |||
expect(parse(`one ${americanName}`)).toBe(value); | |||
value | numberName | |||
${'1e+66'} | ${'one unvigintillion'} | |||
${'1e+69'} | ${'one duovigintillion'} | |||
${'1e+72'} | ${'one trevigintillion'} | |||
${'1e+75'} | ${'one quattuorvigintillion'} | |||
${'1e+78'} | ${'one quinvigintillion'} | |||
${'1e+81'} | ${'one sexvigintillion'} | |||
${'1e+84'} | ${'one septenvigintillion'} | |||
${'1e+87'} | ${'one octovigintillion'} | |||
${'1e+90'} | ${'one novemvigintillion'} | |||
${'1e+93'} | ${'one trigintillion'} | |||
${'1e+123'} | ${'one quadragintillion'} | |||
${'1e+150'} | ${'one novemquadragintillion'} | |||
${'1e+153'} | ${'one quinquagintillion'} | |||
${'1e+156'} | ${'one unquinquagintillion'} | |||
${'1e+183'} | ${'one sexagintillion'} | |||
${'1e+213'} | ${'one septuagintillion'} | |||
${'1e+222'} | ${'one treseptuagintillion'} | |||
${'1e+243'} | ${'one octogintillion'} | |||
${'1e+273'} | ${'one nonagintillion'} | |||
${'1e+300'} | ${'one novemnonagintillion'} | |||
`('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { | |||
expect(stringify(value, stringifyOptions)).toBe(numberName); | |||
expect(parse(numberName)).toBe(value); | |||
}); | |||
}); | |||
describe('Gigantic size numbers (< 1e+3003)', () => { | |||
it.each` | |||
value | americanName | |||
${'1e+303'} | ${'centillion'} | |||
${'1e+306'} | ${'cenuntillion'} | |||
${'1e+309'} | ${'cenduotillion'} | |||
${'1e+312'} | ${'centretillion'} | |||
${'1e+315'} | ${'cenquattuortillion'} | |||
${'1e+318'} | ${'cenquintillion'} | |||
${'1e+321'} | ${'censextillion'} | |||
${'1e+324'} | ${'censeptentillion'} | |||
${'1e+327'} | ${'cenoctotillion'} | |||
${'1e+330'} | ${'cennovemtillion'} | |||
${'1e+603'} | ${'duocentillion'} | |||
${'1e+903'} | ${'trecentillion'} | |||
${'1e+1203'} | ${'quadringentillion'} | |||
${'1e+1503'} | ${'quingentillion'} | |||
${'1e+1803'} | ${'sescentillion'} | |||
${'1e+2103'} | ${'septingentillion'} | |||
${'1e+2403'} | ${'octingentillion'} | |||
${'1e+2703'} | ${'nongentillion'} | |||
`('converts $value to $americanName', ({ value, americanName }: { value: string, americanName: string }) => { | |||
expect(stringify(value, stringifyOptions)).toBe(`one ${americanName}`); | |||
expect(parse(`one ${americanName}`)).toBe(value); | |||
value | numberName | |||
${'1e+303'} | ${'one centillion'} | |||
${'1e+306'} | ${'one cenuntillion'} | |||
${'1e+309'} | ${'one cenduotillion'} | |||
${'1e+312'} | ${'one centretillion'} | |||
${'1e+315'} | ${'one cenquattuortillion'} | |||
${'1e+318'} | ${'one cenquintillion'} | |||
${'1e+321'} | ${'one censextillion'} | |||
${'1e+324'} | ${'one censeptentillion'} | |||
${'1e+327'} | ${'one cenoctotillion'} | |||
${'1e+330'} | ${'one cennovemtillion'} | |||
${'1e+603'} | ${'one duocentillion'} | |||
${'1e+903'} | ${'one trecentillion'} | |||
${'1e+1203'} | ${'one quadringentillion'} | |||
${'1e+1503'} | ${'one quingentillion'} | |||
${'1e+1803'} | ${'one sescentillion'} | |||
${'1e+2103'} | ${'one septingentillion'} | |||
${'1e+2403'} | ${'one octingentillion'} | |||
${'1e+2703'} | ${'one nongentillion'} | |||
`('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { | |||
expect(stringify(value, stringifyOptions)).toBe(numberName); | |||
expect(parse(numberName)).toBe(value); | |||
}); | |||
}); | |||
@@ -183,7 +183,7 @@ describe('Landon\'s original test cases', () => { | |||
); | |||
it.each` | |||
value | americanName | |||
value | numberName | |||
${'1.23456789246801357e+6221'} | ${'one hundred twenty three duomilliaduoseptuagintillion four hundred fifty six duomilliaunseptuagintillion seven hundred eighty nine duomilliaseptuagintillion two hundred forty six duomillianovemsexagintillion eight hundred one duomilliaoctosexagintillion three hundred fifty seven duomilliaseptensexagintillion'} | |||
${'1.23456789246801357e+2961221'} | ${'one hundred twenty three nongenseptenoctoginmilliaduoseptuagintillion four hundred fifty six nongenseptenoctoginmilliaunseptuagintillion seven hundred eighty nine nongenseptenoctoginmilliaseptuagintillion two hundred forty six nongenseptenoctoginmillianovemsexagintillion eight hundred one nongenseptenoctoginmilliaoctosexagintillion three hundred fifty seven nongenseptenoctoginmilliaseptensexagintillion'} | |||
${'123.456789246801357e+2961000000219'} | ${'one hundred twenty three nongenseptenoctoginmilliamilliamilliaduoseptuagintillion four hundred fifty six nongenseptenoctoginmilliamilliamilliaunseptuagintillion seven hundred eighty nine nongenseptenoctoginmilliamilliamilliaseptuagintillion two hundred forty six nongenseptenoctoginmilliamilliamillianovemsexagintillion eight hundred one nongenseptenoctoginmilliamilliamilliaoctosexagintillion three hundred fifty seven nongenseptenoctoginmilliamilliamilliaseptensexagintillion'} | |||
@@ -195,9 +195,9 @@ describe('Landon\'s original test cases', () => { | |||
${'1e+1174743648579'} | ${'one trecenunnonaginmilliamilliamilliaquingenunoctoginmilliamilliaduocensexdecmilliacenduononagintillion'} | |||
${'1e+3000000000003'} | ${'one milliamilliamilliamilliatillion'} | |||
${'1e+696276510359811'} | ${'one duocenduotriginmilliamilliamilliamilliaduononaginmilliamilliamilliacenseptuaginmilliamilliacennovemdecmillianongensextrigintillion'} | |||
`('converts $value to $americanName', ({ value, americanName }: { value: string, americanName: string }) => { | |||
expect(stringify(value, stringifyOptions)).toBe(americanName); | |||
expect(parse(americanName)).toBe(numberToExponential(value)); | |||
`('converts $value to $numberName', ({ value, numberName }: { value: string, numberName: string }) => { | |||
expect(stringify(value, stringifyOptions)).toBe(numberName); | |||
expect(parse(numberName)).toBe(numberToExponential(value)); | |||
}); | |||
}); | |||
}); |
@@ -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'); | |||
}); | |||
}); |