|
@@ -1,14 +1,29 @@ |
|
|
// noinspection SpellCheckingInspection |
|
|
// noinspection SpellCheckingInspection |
|
|
|
|
|
|
|
|
import { Group } from '../common'; |
|
|
|
|
|
|
|
|
import { Group, InvalidTokenError } from '../common'; |
|
|
import { numberToExponential } from '../exponent'; |
|
|
import { numberToExponential } from '../exponent'; |
|
|
|
|
|
|
|
|
const DECIMAL_POINT = '.'; |
|
|
|
|
|
|
|
|
const DECIMAL_POINT = '.' as const; |
|
|
|
|
|
|
|
|
const GROUPING_SYMBOL = ','; |
|
|
|
|
|
|
|
|
const GROUPING_SYMBOL = ',' as const; |
|
|
|
|
|
|
|
|
const NEGATIVE = 'negative'; |
|
|
|
|
|
|
|
|
const NEGATIVE = 'negative' as const; |
|
|
|
|
|
|
|
|
|
|
|
const NEGATIVE_SYMBOL = '-' as const; |
|
|
|
|
|
|
|
|
|
|
|
const POSITIVE_SYMBOL = '+' as const; |
|
|
|
|
|
|
|
|
|
|
|
const SHORT_MILLIA_DELIMITER = '^' as const; |
|
|
|
|
|
|
|
|
|
|
|
const EXPONENT_DELIMITER = 'e' as const; |
|
|
|
|
|
|
|
|
|
|
|
const EMPTY_GROUP_DIGITS = '000' as const; |
|
|
|
|
|
|
|
|
|
|
|
const EMPTY_PLACE: Group = [EMPTY_GROUP_DIGITS, 0]; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Ones number names. |
|
|
|
|
|
*/ |
|
|
const ONES = [ |
|
|
const ONES = [ |
|
|
'zero', |
|
|
'zero', |
|
|
'one', |
|
|
'one', |
|
@@ -24,6 +39,9 @@ const ONES = [ |
|
|
|
|
|
|
|
|
type OnesName = typeof ONES[number]; |
|
|
type OnesName = typeof ONES[number]; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Ten plus ones number names. |
|
|
|
|
|
*/ |
|
|
const TEN_PLUS_ONES = [ |
|
|
const TEN_PLUS_ONES = [ |
|
|
'ten', |
|
|
'ten', |
|
|
'eleven', |
|
|
'eleven', |
|
@@ -39,6 +57,9 @@ const TEN_PLUS_ONES = [ |
|
|
|
|
|
|
|
|
type TenPlusOnesName = typeof TEN_PLUS_ONES[number]; |
|
|
type TenPlusOnesName = typeof TEN_PLUS_ONES[number]; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Tens number names. |
|
|
|
|
|
*/ |
|
|
const TENS = [ |
|
|
const TENS = [ |
|
|
'zero', |
|
|
'zero', |
|
|
TEN_PLUS_ONES[0], |
|
|
TEN_PLUS_ONES[0], |
|
@@ -54,14 +75,23 @@ const TENS = [ |
|
|
|
|
|
|
|
|
type TensName = typeof TENS[number]; |
|
|
type TensName = typeof TENS[number]; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Hundreds name. |
|
|
|
|
|
*/ |
|
|
const HUNDRED = 'hundred' as const; |
|
|
const HUNDRED = 'hundred' as const; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Thousands name. |
|
|
|
|
|
*/ |
|
|
const THOUSAND = 'thousand' as const; |
|
|
const THOUSAND = 'thousand' as const; |
|
|
|
|
|
|
|
|
// const ILLION_ORDINAL_SUFFIX = 'illionth' as const; |
|
|
// const ILLION_ORDINAL_SUFFIX = 'illionth' as const; |
|
|
|
|
|
|
|
|
// const THOUSAND_ORDINAL = 'thousandth' as const; |
|
|
// const THOUSAND_ORDINAL = 'thousandth' as const; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Special millions name. |
|
|
|
|
|
*/ |
|
|
const MILLIONS_SPECIAL_PREFIXES = [ |
|
|
const MILLIONS_SPECIAL_PREFIXES = [ |
|
|
'', |
|
|
'', |
|
|
'm', |
|
|
'm', |
|
@@ -75,8 +105,11 @@ const MILLIONS_SPECIAL_PREFIXES = [ |
|
|
'non', |
|
|
'non', |
|
|
] as const; |
|
|
] as const; |
|
|
|
|
|
|
|
|
type MillionsSpecialPrefix = typeof MILLIONS_SPECIAL_PREFIXES[number]; |
|
|
|
|
|
|
|
|
type MillionsSpecialPrefix = Exclude<typeof MILLIONS_SPECIAL_PREFIXES[number], ''>; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Millions name. |
|
|
|
|
|
*/ |
|
|
const MILLIONS_PREFIXES = [ |
|
|
const MILLIONS_PREFIXES = [ |
|
|
'', |
|
|
'', |
|
|
'un', |
|
|
'un', |
|
@@ -90,8 +123,11 @@ const MILLIONS_PREFIXES = [ |
|
|
'novem', |
|
|
'novem', |
|
|
] as const; |
|
|
] as const; |
|
|
|
|
|
|
|
|
type MillionsPrefix = typeof MILLIONS_PREFIXES[number]; |
|
|
|
|
|
|
|
|
type MillionsPrefix = Exclude<typeof MILLIONS_PREFIXES[number], ''>; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Decillions name. |
|
|
|
|
|
*/ |
|
|
const DECILLIONS_PREFIXES = [ |
|
|
const DECILLIONS_PREFIXES = [ |
|
|
'', |
|
|
'', |
|
|
'dec', |
|
|
'dec', |
|
@@ -105,8 +141,11 @@ const DECILLIONS_PREFIXES = [ |
|
|
'nonagin', |
|
|
'nonagin', |
|
|
] as const; |
|
|
] as const; |
|
|
|
|
|
|
|
|
type DecillionsPrefix = typeof DECILLIONS_PREFIXES[number]; |
|
|
|
|
|
|
|
|
type DecillionsPrefix = Exclude<typeof DECILLIONS_PREFIXES[number], ''>; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Centillions name. |
|
|
|
|
|
*/ |
|
|
const CENTILLIONS_PREFIXES = [ |
|
|
const CENTILLIONS_PREFIXES = [ |
|
|
'', |
|
|
'', |
|
|
'cen', |
|
|
'cen', |
|
@@ -120,12 +159,24 @@ const CENTILLIONS_PREFIXES = [ |
|
|
'nongen', |
|
|
'nongen', |
|
|
] as const; |
|
|
] as const; |
|
|
|
|
|
|
|
|
type CentillionsPrefix = typeof CENTILLIONS_PREFIXES[number]; |
|
|
|
|
|
|
|
|
type CentillionsPrefix = Exclude<typeof CENTILLIONS_PREFIXES[number], ''>; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Prefix for millia- number names. |
|
|
|
|
|
*/ |
|
|
const MILLIA_PREFIX = 'millia' as const; |
|
|
const MILLIA_PREFIX = 'millia' as const; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Suffix for -illion number names. |
|
|
|
|
|
*/ |
|
|
const ILLION_SUFFIX = 'illion' as const; |
|
|
const ILLION_SUFFIX = 'illion' as const; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Builds a name for numbers in tens and ones. |
|
|
|
|
|
* @param tens - Tens digit. |
|
|
|
|
|
* @param ones - Ones digit. |
|
|
|
|
|
* @returns The name for the number. |
|
|
|
|
|
*/ |
|
|
const makeTensName = (tens: number, ones: number) => { |
|
|
const makeTensName = (tens: number, ones: number) => { |
|
|
if (tens === 0) { |
|
|
if (tens === 0) { |
|
|
return ONES[ones]; |
|
|
return ONES[ones]; |
|
@@ -142,6 +193,13 @@ const makeTensName = (tens: number, ones: number) => { |
|
|
return `${TENS[tens] as Exclude<TensName, 'zero' | 'ten'>} ${ONES[ones] as Exclude<OnesName, 'zero'>}` as const; |
|
|
return `${TENS[tens] as Exclude<TensName, 'zero' | 'ten'>} ${ONES[ones] as Exclude<OnesName, 'zero'>}` as const; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Builds a name for numbers in hundreds, tens, and ones. |
|
|
|
|
|
* @param hundreds - Hundreds digit. |
|
|
|
|
|
* @param tens - Tens digit. |
|
|
|
|
|
* @param ones - Ones digit. |
|
|
|
|
|
* @returns The name for the number. |
|
|
|
|
|
*/ |
|
|
const makeHundredsName = (hundreds: number, tens: number, ones: number) => { |
|
|
const makeHundredsName = (hundreds: number, tens: number, ones: number) => { |
|
|
if (hundreds === 0) { |
|
|
if (hundreds === 0) { |
|
|
return makeTensName(tens, ones); |
|
|
return makeTensName(tens, ones); |
|
@@ -154,24 +212,45 @@ const makeHundredsName = (hundreds: number, tens: number, ones: number) => { |
|
|
return `${ONES[hundreds]} ${HUNDRED} ${makeTensName(tens, ones)}` as const; |
|
|
return `${ONES[hundreds]} ${HUNDRED} ${makeTensName(tens, ones)}` as const; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Builds a name for numbers in the millions. |
|
|
|
|
|
* @param millions - Millions digit. |
|
|
|
|
|
* @param milliaCount - Number of millia- groups. |
|
|
|
|
|
* @returns The millions prefix. |
|
|
|
|
|
*/ |
|
|
const makeMillionsPrefix = (millions: number, milliaCount: number) => { |
|
|
const makeMillionsPrefix = (millions: number, milliaCount: number) => { |
|
|
if (milliaCount > 0) { |
|
|
if (milliaCount > 0) { |
|
|
return MILLIONS_PREFIXES[millions] as Exclude<MillionsPrefix, ''>; |
|
|
|
|
|
|
|
|
return MILLIONS_PREFIXES[millions] as MillionsPrefix; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return MILLIONS_SPECIAL_PREFIXES[millions] as Exclude<MillionsSpecialPrefix, ''>; |
|
|
|
|
|
|
|
|
return MILLIONS_SPECIAL_PREFIXES[millions] as MillionsSpecialPrefix; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Builds a name for numbers in the decillions. |
|
|
|
|
|
* @param decillions - Decillions digit. |
|
|
|
|
|
* @param millions - Millions digit. |
|
|
|
|
|
* @param milliaCount - Number of millia- groups. |
|
|
|
|
|
* @returns The decillions prefix. |
|
|
|
|
|
*/ |
|
|
const makeDecillionsPrefix = (decillions: number, millions: number, milliaCount: number) => { |
|
|
const makeDecillionsPrefix = (decillions: number, millions: number, milliaCount: number) => { |
|
|
if (decillions === 0) { |
|
|
if (decillions === 0) { |
|
|
return makeMillionsPrefix(millions, milliaCount); |
|
|
return makeMillionsPrefix(millions, milliaCount); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const onesPrefix = MILLIONS_PREFIXES[millions] as Exclude<MillionsPrefix, ''>; |
|
|
|
|
|
const tensName = DECILLIONS_PREFIXES[decillions] as Exclude<DecillionsPrefix, ''>; |
|
|
|
|
|
|
|
|
const onesPrefix = MILLIONS_PREFIXES[millions] as MillionsPrefix; |
|
|
|
|
|
const tensName = DECILLIONS_PREFIXES[decillions] as DecillionsPrefix; |
|
|
return `${onesPrefix}${tensName}` as const; |
|
|
return `${onesPrefix}${tensName}` as const; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Builds a name for numbers in the centillions. |
|
|
|
|
|
* @param centillions - Centillions digit. |
|
|
|
|
|
* @param decillions - Decillions digit. |
|
|
|
|
|
* @param millions - Millions digit. |
|
|
|
|
|
* @param milliaCount - Number of millia- groups. |
|
|
|
|
|
* @returns The centillions prefix. |
|
|
|
|
|
*/ |
|
|
const makeCentillionsPrefix = ( |
|
|
const makeCentillionsPrefix = ( |
|
|
centillions: number, |
|
|
centillions: number, |
|
|
decillions: number, |
|
|
decillions: number, |
|
@@ -182,9 +261,9 @@ const makeCentillionsPrefix = ( |
|
|
return makeDecillionsPrefix(decillions, millions, milliaCount); |
|
|
return makeDecillionsPrefix(decillions, millions, milliaCount); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const onesPrefix = MILLIONS_PREFIXES[millions] as Exclude<MillionsPrefix, ''>; |
|
|
|
|
|
const tensName = DECILLIONS_PREFIXES[decillions] as Exclude<DecillionsPrefix, ''>; |
|
|
|
|
|
const hundredsName = CENTILLIONS_PREFIXES[centillions] as Exclude<CentillionsPrefix, ''>; |
|
|
|
|
|
|
|
|
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; |
|
|
return `${hundredsName}${onesPrefix}${decillions > 0 ? tensName : ''}` as const; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
@@ -220,21 +299,21 @@ const getGroupName = (place: number, shortenMillia: boolean) => { |
|
|
}, |
|
|
}, |
|
|
[], |
|
|
[], |
|
|
) |
|
|
) |
|
|
.map(([group, groupPlace]) => [group.padStart(3, '0'), groupPlace] as const) |
|
|
|
|
|
.filter(([group]) => group !== '000') |
|
|
|
|
|
.map(([group, groupPlace]) => { |
|
|
|
|
|
const [hundreds, tens, ones] = group.split('').map(Number); |
|
|
|
|
|
|
|
|
.map(([groupDigits, groupPlace]) => [groupDigits.padStart(3, '0'), groupPlace] as const) |
|
|
|
|
|
.filter(([groupDigits]) => groupDigits !== EMPTY_GROUP_DIGITS) |
|
|
|
|
|
.map(([groupDigits, groupPlace]) => { |
|
|
|
|
|
const [hundreds, tens, ones] = groupDigits.split('').map(Number); |
|
|
if (groupPlace < 1) { |
|
|
if (groupPlace < 1) { |
|
|
return makeCentillionsPrefix(hundreds, tens, ones, groupPlace); |
|
|
return makeCentillionsPrefix(hundreds, tens, ones, groupPlace); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const milliaSuffix = ( |
|
|
const milliaSuffix = ( |
|
|
shortenMillia && groupPlace > 1 |
|
|
shortenMillia && groupPlace > 1 |
|
|
? `${MILLIA_PREFIX}^${groupPlace}` |
|
|
|
|
|
|
|
|
? `${MILLIA_PREFIX}${SHORT_MILLIA_DELIMITER}${groupPlace}` |
|
|
: MILLIA_PREFIX.repeat(groupPlace) |
|
|
: MILLIA_PREFIX.repeat(groupPlace) |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
if (group === '001') { |
|
|
|
|
|
|
|
|
if (groupDigits === '001') { |
|
|
return milliaSuffix; |
|
|
return milliaSuffix; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@@ -276,22 +355,21 @@ export const makeGroup = ( |
|
|
* @param value - The number string to group. |
|
|
* @param value - The number string to group. |
|
|
*/ |
|
|
*/ |
|
|
export const group = (value: string): Group[] => { |
|
|
export const group = (value: string): Group[] => { |
|
|
const exponentDelimiter = 'e'; |
|
|
|
|
|
const [significand, exponentString] = numberToExponential( |
|
|
const [significand, exponentString] = numberToExponential( |
|
|
value, |
|
|
value, |
|
|
{ |
|
|
{ |
|
|
decimalPoint: DECIMAL_POINT, |
|
|
decimalPoint: DECIMAL_POINT, |
|
|
groupingSymbol: GROUPING_SYMBOL, |
|
|
groupingSymbol: GROUPING_SYMBOL, |
|
|
exponentDelimiter, |
|
|
|
|
|
|
|
|
exponentDelimiter: EXPONENT_DELIMITER, |
|
|
}, |
|
|
}, |
|
|
) |
|
|
) |
|
|
.split(exponentDelimiter); |
|
|
|
|
|
|
|
|
.split(EXPONENT_DELIMITER); |
|
|
const exponent = Number(exponentString); |
|
|
const exponent = Number(exponentString); |
|
|
const significantDigits = significand.replace(DECIMAL_POINT, ''); |
|
|
const significantDigits = significand.replace(DECIMAL_POINT, ''); |
|
|
return significantDigits.split('').reduce<Group[]>( |
|
|
return significantDigits.split('').reduce<Group[]>( |
|
|
(acc, c, i) => { |
|
|
(acc, c, i) => { |
|
|
const currentPlace = Math.floor((exponent - i) / 3); |
|
|
const currentPlace = Math.floor((exponent - i) / 3); |
|
|
const lastGroup = acc.at(-1) ?? ['000', currentPlace]; |
|
|
|
|
|
|
|
|
const lastGroup = acc.at(-1) ?? [EMPTY_GROUP_DIGITS, currentPlace]; |
|
|
const currentPlaceInGroup = 2 - ((exponent - i) % 3); |
|
|
const currentPlaceInGroup = 2 - ((exponent - i) % 3); |
|
|
if (lastGroup[1] === currentPlace) { |
|
|
if (lastGroup[1] === currentPlace) { |
|
|
const lastGroupDigits = lastGroup[0].split(''); |
|
|
const lastGroupDigits = lastGroup[0].split(''); |
|
@@ -330,95 +408,154 @@ export const tokenize = (stringValue: string) => ( |
|
|
stringValue.split(' ').filter((maybeToken) => maybeToken.length > 0) |
|
|
stringValue.split(' ').filter((maybeToken) => maybeToken.length > 0) |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
const FINAL_TOKEN = ''; |
|
|
|
|
|
|
|
|
const FINAL_TOKEN = '' as const; |
|
|
|
|
|
|
|
|
const getGroupFromGroupName = (groupName: string) => { |
|
|
|
|
|
if (groupName === THOUSAND) { |
|
|
|
|
|
return 1; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const groupNameBase = groupName.replace(ILLION_SUFFIX, ''); |
|
|
|
|
|
const specialMillions = MILLIONS_SPECIAL_PREFIXES.findIndex((p) => groupNameBase === p); |
|
|
|
|
|
|
|
|
interface DoParseState { |
|
|
|
|
|
groupNameCurrent: string; |
|
|
|
|
|
millias: number[]; |
|
|
|
|
|
milliaIndex: number; |
|
|
|
|
|
done: boolean; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (specialMillions > -1) { |
|
|
|
|
|
return 1 + specialMillions; |
|
|
|
|
|
|
|
|
const doParseGroupName = (result: DoParseState): DoParseState => { |
|
|
|
|
|
if (result.groupNameCurrent.length < 1) { |
|
|
|
|
|
return { |
|
|
|
|
|
...result, |
|
|
|
|
|
done: true, |
|
|
|
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
let groupNameCurrent = groupNameBase; |
|
|
|
|
|
|
|
|
if (result.groupNameCurrent === 't') { |
|
|
|
|
|
// 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. |
|
|
|
|
|
return { |
|
|
|
|
|
...result, |
|
|
|
|
|
done: true, |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const millias = [0]; |
|
|
|
|
|
let milliaIndex = 0; |
|
|
|
|
|
|
|
|
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, |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
while (groupNameCurrent.length > 0) { |
|
|
|
|
|
if (groupNameCurrent === 't') { |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
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 centillions = CENTILLIONS_PREFIXES.findIndex((p) => ( |
|
|
|
|
|
p.length > 0 && groupNameCurrent.startsWith(p) |
|
|
|
|
|
)); |
|
|
|
|
|
|
|
|
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 (centillions > -1) { |
|
|
|
|
|
milliaIndex = 0; |
|
|
|
|
|
millias[milliaIndex] += (centillions * 100); |
|
|
|
|
|
groupNameCurrent = groupNameCurrent.slice(CENTILLIONS_PREFIXES[centillions].length); |
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
if (result.groupNameCurrent.startsWith(`${MILLIA_PREFIX}${SHORT_MILLIA_DELIMITER}`)) { |
|
|
|
|
|
// short millia |
|
|
|
|
|
const matchedMilliaArray = result.groupNameCurrent.match(/^\d+/); |
|
|
|
|
|
if (!matchedMilliaArray) { |
|
|
|
|
|
throw new InvalidTokenError(result.groupNameCurrent); |
|
|
} |
|
|
} |
|
|
|
|
|
const matchedMillia = matchedMilliaArray[0]; |
|
|
|
|
|
const newMillia = Number(matchedMillia); |
|
|
|
|
|
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(MILLIA_PREFIX.length), |
|
|
|
|
|
done: false, |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const decillions = DECILLIONS_PREFIXES.findIndex((p) => ( |
|
|
|
|
|
p.length > 0 && groupNameCurrent.startsWith(p) |
|
|
|
|
|
)); |
|
|
|
|
|
|
|
|
if (result.groupNameCurrent.startsWith(MILLIA_PREFIX)) { |
|
|
|
|
|
const newMillia = result.milliaIndex + 1; |
|
|
|
|
|
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(MILLIA_PREFIX.length), |
|
|
|
|
|
done: false, |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (decillions > -1) { |
|
|
|
|
|
milliaIndex = 0; |
|
|
|
|
|
millias[milliaIndex] += decillions * 10; |
|
|
|
|
|
groupNameCurrent = groupNameCurrent.slice(DECILLIONS_PREFIXES[decillions].length); |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
throw new InvalidTokenError(result.groupNameCurrent); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
const millions = MILLIONS_PREFIXES.findIndex((p) => ( |
|
|
|
|
|
p.length > 0 && groupNameCurrent.startsWith(p) |
|
|
|
|
|
)); |
|
|
|
|
|
|
|
|
const getGroupPlaceFromGroupName = (groupName: string) => { |
|
|
|
|
|
if (groupName === THOUSAND) { |
|
|
|
|
|
return 1; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (millions > -1) { |
|
|
|
|
|
milliaIndex = 0; |
|
|
|
|
|
millias[milliaIndex] += millions; |
|
|
|
|
|
groupNameCurrent = groupNameCurrent.slice(MILLIONS_PREFIXES[millions].length); |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const groupNameBase = groupName.replace(ILLION_SUFFIX, ''); |
|
|
|
|
|
const specialMillions = MILLIONS_SPECIAL_PREFIXES.findIndex((p) => groupNameBase === p); |
|
|
|
|
|
|
|
|
if (groupNameCurrent.startsWith(`${MILLIA_PREFIX}^`)) { |
|
|
|
|
|
// short millia |
|
|
|
|
|
groupNameCurrent = groupNameCurrent.slice(MILLIA_PREFIX.length); |
|
|
|
|
|
const matchedMilliaArray = groupNameCurrent.match(/^\d+/); |
|
|
|
|
|
if (!matchedMilliaArray) { |
|
|
|
|
|
throw new Error(`Invalid groupName: ${groupName}`); |
|
|
|
|
|
} |
|
|
|
|
|
const matchedMillia = matchedMilliaArray[0]; |
|
|
|
|
|
millias[Number(matchedMillia)] = millias[milliaIndex] || 1; |
|
|
|
|
|
millias[milliaIndex] = 0; |
|
|
|
|
|
groupNameCurrent = groupNameCurrent.slice(matchedMillia.length); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (specialMillions > -1) { |
|
|
|
|
|
return specialMillions + 1; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (groupNameCurrent.startsWith(MILLIA_PREFIX)) { |
|
|
|
|
|
millias[milliaIndex + 1] = millias[milliaIndex] || 1; |
|
|
|
|
|
millias[milliaIndex] = 0; |
|
|
|
|
|
milliaIndex += 1; |
|
|
|
|
|
groupNameCurrent = groupNameCurrent.slice(MILLIA_PREFIX.length); |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
let result: DoParseState = { |
|
|
|
|
|
groupNameCurrent: groupNameBase, |
|
|
|
|
|
millias: [0], |
|
|
|
|
|
milliaIndex: 0, |
|
|
|
|
|
done: false, |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
do { |
|
|
|
|
|
result = doParseGroupName(result); |
|
|
|
|
|
} while (!result.done); |
|
|
|
|
|
|
|
|
const bigGroupPlace = Number( |
|
|
const bigGroupPlace = Number( |
|
|
millias |
|
|
|
|
|
|
|
|
result.millias |
|
|
.map((s) => s.toString().padStart(3, '0')) |
|
|
.map((s) => s.toString().padStart(3, '0')) |
|
|
.reverse() |
|
|
.reverse() |
|
|
.join(''), |
|
|
.join(''), |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
return 1 + bigGroupPlace; |
|
|
|
|
|
|
|
|
return bigGroupPlace + 1; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
enum ParseGroupsMode { |
|
|
enum ParseGroupsMode { |
|
@@ -440,7 +577,7 @@ interface ParserState { |
|
|
export const parseGroups = (tokens: string[]) => { |
|
|
export const parseGroups = (tokens: string[]) => { |
|
|
const { groups } = [...tokens, FINAL_TOKEN].reduce<ParserState>( |
|
|
const { groups } = [...tokens, FINAL_TOKEN].reduce<ParserState>( |
|
|
(acc, token) => { |
|
|
(acc, token) => { |
|
|
const lastGroup = acc.groups.at(-1) ?? ['000', 0]; |
|
|
|
|
|
|
|
|
const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; |
|
|
|
|
|
|
|
|
if (token === THOUSAND || token.endsWith(ILLION_SUFFIX)) { |
|
|
if (token === THOUSAND || token.endsWith(ILLION_SUFFIX)) { |
|
|
if (acc.mode === ParseGroupsMode.ONES_MODE) { |
|
|
if (acc.mode === ParseGroupsMode.ONES_MODE) { |
|
@@ -448,7 +585,7 @@ export const parseGroups = (tokens: string[]) => { |
|
|
lastGroup[0] = `${lastGroup[0].slice(0, 2)}${ones}`; |
|
|
lastGroup[0] = `${lastGroup[0].slice(0, 2)}${ones}`; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
lastGroup[1] = getGroupFromGroupName(token); |
|
|
|
|
|
|
|
|
lastGroup[1] = getGroupPlaceFromGroupName(token); |
|
|
|
|
|
|
|
|
return { |
|
|
return { |
|
|
...acc, |
|
|
...acc, |
|
@@ -489,7 +626,7 @@ export const parseGroups = (tokens: string[]) => { |
|
|
...acc, |
|
|
...acc, |
|
|
lastToken: token, |
|
|
lastToken: token, |
|
|
mode: ParseGroupsMode.ONES_MODE, |
|
|
mode: ParseGroupsMode.ONES_MODE, |
|
|
groups: [...acc.groups, ['000', 0]], |
|
|
|
|
|
|
|
|
groups: [...acc.groups, [...EMPTY_PLACE]], |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
return { |
|
|
return { |
|
@@ -551,11 +688,14 @@ export const combineGroups = (groups: Group[]) => { |
|
|
const firstGroupDigitsWithoutZeroes = firstGroupDigits.replace(/^0+/, ''); |
|
|
const firstGroupDigitsWithoutZeroes = firstGroupDigits.replace(/^0+/, ''); |
|
|
const exponentExtra = firstGroupDigits.length - firstGroupDigitsWithoutZeroes.length; |
|
|
const exponentExtra = firstGroupDigits.length - firstGroupDigitsWithoutZeroes.length; |
|
|
const exponentValue = BigInt((firstGroupPlace * 3) + (2 - exponentExtra)); |
|
|
const exponentValue = BigInt((firstGroupPlace * 3) + (2 - exponentExtra)); |
|
|
const exponent = exponentValue < 0 ? exponentValue.toString() : `+${exponentValue}`; |
|
|
|
|
|
|
|
|
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 significandInteger = digits.slice(0, 1); |
|
|
const significandFraction = digits.slice(1); |
|
|
const significandFraction = digits.slice(1); |
|
|
if (significandFraction.length > 0) { |
|
|
if (significandFraction.length > 0) { |
|
|
return `${significandInteger}${DECIMAL_POINT}${significandFraction}e${exponent}`; |
|
|
|
|
|
|
|
|
return `${significandInteger}${DECIMAL_POINT}${significandFraction}${EXPONENT_DELIMITER}${exponent}`; |
|
|
} |
|
|
} |
|
|
return `${significandInteger}e${exponent}`; |
|
|
|
|
|
|
|
|
return `${significandInteger}${EXPONENT_DELIMITER}${exponent}`; |
|
|
}; |
|
|
}; |