Add features and limitations to the project. Also update the types to be stricter.master
@@ -5,3 +5,22 @@ Get the name of a number, even if it's stupidly big. | |||||
## References | ## References | ||||
* [How high can you count?](http://www.isthe.com/chongo/tech/math/number/howhigh.html) | * [How high can you count?](http://www.isthe.com/chongo/tech/math/number/howhigh.html) | ||||
## Features | |||||
* Stringify/parse numbers and names in American/British short count (e.g. "million", "billion", "trillion"), or European | |||||
(e.g. "million", "milliard", "billion", "billiard", "trillion", "trilliard"). | |||||
* Support for exponential notation, even if in non-standard form (e.g. `123.45e+5`). | |||||
* Support for negative numbers. | |||||
See [TODO.md](TODO.md) for a list of features that are planned for implementation. | |||||
## Limitations | |||||
* Can only stringify and parse numbers and names that resolve to integer values. | |||||
* Exponents `x` in values are limited to `Number.MAX_SAFE_INTEGER >~ x >~ Number.MIN_SAFE_INTEGER`. | |||||
Values may exceed extremes such as `Number.MAX_SAFE_INTEGER` and `Number.EPSILON` in which loss of | |||||
precision may occur. | |||||
* No support for arbitrary number names such as "googol". | |||||
* No support for fractional number names such as "half", "quarter", "third", "tenth", etc. | |||||
* Supports only native types (`bigint`, `string`, `number`). |
@@ -1,3 +1,3 @@ | |||||
- [ ] Ordinals | - [ ] Ordinals | ||||
- [ ] Fractions | |||||
- [ ] Other locales (long count, languages, etc.) | |||||
- [ ] ~~Fractions~~ | |||||
- [X] Other locales (long count, languages, etc.) |
@@ -1,7 +1,9 @@ | |||||
type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'; | |||||
/** | /** | ||||
* Group digits. | * Group digits. | ||||
*/ | */ | ||||
type GroupDigits = string; | |||||
export type GroupDigits = `${Digit}${Digit}${Digit}`; | |||||
/** | /** | ||||
* Group place. | * Group place. | ||||
@@ -102,7 +104,7 @@ export interface NumberNameSystem { | |||||
* Parses groups from a string. | * Parses groups from a string. | ||||
* @param tokens - The string to parse groups from. | * @param tokens - The string to parse groups from. | ||||
* @see {NumberNameSystem.stringifyGroups} | * @see {NumberNameSystem.stringifyGroups} | ||||
* @returns Group[] The parsed groups. | |||||
* @returns ParseResult The parsed groups as well as an indicator if the number is Boolean. | |||||
*/ | */ | ||||
parseGroups: (tokens: string[]) => ParseResult; | parseGroups: (tokens: string[]) => ParseResult; | ||||
/** | /** | ||||
@@ -115,6 +117,25 @@ export interface NumberNameSystem { | |||||
combineGroups: (groups: Group[], negative: boolean) => string; | combineGroups: (groups: Group[], negative: boolean) => string; | ||||
} | } | ||||
/** | |||||
* Allowed value type for {@link stringify}. | |||||
*/ | |||||
export type AllowedValue = string | number | bigint; | |||||
/** | |||||
* Array of allowed types for {@link parse}. | |||||
*/ | |||||
export const ALLOWED_PARSE_RESULT_TYPES = [ | |||||
'string', | |||||
'number', | |||||
'bigint', | |||||
] as const; | |||||
/** | |||||
* Allowed type for {@link parse}. | |||||
*/ | |||||
export type ParseResultType = typeof ALLOWED_PARSE_RESULT_TYPES[number]; | |||||
/** | /** | ||||
* Error thrown when an invalid token is encountered. | * Error thrown when an invalid token is encountered. | ||||
*/ | */ | ||||
@@ -124,6 +145,11 @@ export class InvalidTokenError extends Error { | |||||
} | } | ||||
} | } | ||||
/** | |||||
* Gets the maximum value from a list of bigints. | |||||
* @param b - The bigints to get the maximum value from. | |||||
* @returns bigint The maximum value. | |||||
*/ | |||||
export const bigIntMax = (...b: bigint[]) => b.reduce( | export const bigIntMax = (...b: bigint[]) => b.reduce( | ||||
(previousMax, current) => { | (previousMax, current) => { | ||||
if (typeof previousMax === 'undefined') { | if (typeof previousMax === 'undefined') { | ||||
@@ -134,6 +160,11 @@ export const bigIntMax = (...b: bigint[]) => b.reduce( | |||||
undefined as bigint | undefined, | undefined as bigint | undefined, | ||||
); | ); | ||||
/** | |||||
* Gets the minimum value from a list of bigints. | |||||
* @param b - The bigints to get the minimum value from. | |||||
* @returns bigint The minimum value. | |||||
*/ | |||||
export const bigIntMin = (...b: bigint[]) => b.reduce( | export const bigIntMin = (...b: bigint[]) => b.reduce( | ||||
(previousMin, current) => { | (previousMin, current) => { | ||||
if (typeof previousMin === 'undefined') { | if (typeof previousMin === 'undefined') { | ||||
@@ -1,5 +1,10 @@ | |||||
import { enUS } from './systems'; | import { enUS } from './systems'; | ||||
import { NumberNameSystem } from './common'; | |||||
import { | |||||
ALLOWED_PARSE_RESULT_TYPES, | |||||
AllowedValue, | |||||
NumberNameSystem, | |||||
ParseResultType, | |||||
} from './common'; | |||||
import { exponentialToNumberString, extractExponentialComponents, numberToExponential } from './exponent'; | import { exponentialToNumberString, extractExponentialComponents, numberToExponential } from './exponent'; | ||||
/** | /** | ||||
@@ -12,25 +17,6 @@ const NEGATIVE_SYMBOL = '-' as const; | |||||
*/ | */ | ||||
const EXPONENT_DELIMITER = 'e' as const; | const EXPONENT_DELIMITER = 'e' as const; | ||||
/** | |||||
* Allowed value type for {@link stringify}. | |||||
*/ | |||||
export type AllowedValue = string | number | bigint; | |||||
/** | |||||
* Array of allowed types for {@link parse}. | |||||
*/ | |||||
const ALLOWED_PARSE_RESULT_TYPES = [ | |||||
'string', | |||||
'number', | |||||
'bigint', | |||||
] as const; | |||||
/** | |||||
* Allowed type for {@link parse}. | |||||
*/ | |||||
type ParseResult = typeof ALLOWED_PARSE_RESULT_TYPES[number]; | |||||
/** | /** | ||||
* Options to use when converting a value to a string. | * Options to use when converting a value to a string. | ||||
*/ | */ | ||||
@@ -104,7 +90,7 @@ export interface ParseOptions { | |||||
/** | /** | ||||
* The type to parse the value as. | * The type to parse the value as. | ||||
*/ | */ | ||||
type?: ParseResult; | |||||
type?: ParseResultType; | |||||
} | } | ||||
/** | /** | ||||
@@ -115,7 +101,7 @@ export interface ParseOptions { | |||||
*/ | */ | ||||
export const parse = (value: string, options = {} as ParseOptions) => { | export const parse = (value: string, options = {} as ParseOptions) => { | ||||
const { system = enUS.shortCount, type: typeRaw = 'string' } = options; | const { system = enUS.shortCount, type: typeRaw = 'string' } = options; | ||||
const type = typeRaw.trim().toLowerCase() as ParseResult; | |||||
const type = typeRaw.trim().toLowerCase() as ParseResultType; | |||||
if (!((ALLOWED_PARSE_RESULT_TYPES as unknown as string[]).includes(type))) { | if (!((ALLOWED_PARSE_RESULT_TYPES as unknown as string[]).includes(type))) { | ||||
throw new TypeError(`Return type must be a string, number, or bigint. Received: ${type}`); | throw new TypeError(`Return type must be a string, number, or bigint. Received: ${type}`); | ||||
@@ -1,7 +1,4 @@ | |||||
/** | |||||
* Valid values that can be converted to exponential notation. | |||||
*/ | |||||
export type ValidValue = string | number | bigint; | |||||
import { AllowedValue } from './common'; | |||||
interface BaseOptions { | interface BaseOptions { | ||||
/** | /** | ||||
@@ -158,13 +155,13 @@ export const extractExponentialComponents = ( | |||||
}; | }; | ||||
/** | /** | ||||
* Converts a numeric value to a string in exponential notation. Supports numbers of all types. | |||||
* Converts a numeric value to a string in exponential notation. | |||||
* @param value - The value to convert. | * @param value - The value to convert. | ||||
* @param options - Options to use when extracting components. | * @param options - Options to use when extracting components. | ||||
* @returns string The value in exponential notation. | * @returns string The value in exponential notation. | ||||
*/ | */ | ||||
export const numberToExponential = ( | export const numberToExponential = ( | ||||
value: ValidValue, | |||||
value: AllowedValue, | |||||
options = {} as NumberToExponentialOptions, | options = {} as NumberToExponentialOptions, | ||||
): string => { | ): string => { | ||||
const valueRaw = value as unknown; | const valueRaw = value as unknown; | ||||
@@ -216,6 +213,12 @@ export const numberToExponential = ( | |||||
return `${significandInteger}${decimalPoint}${significandFractional}${exponentDelimiter}${exponent}`; | return `${significandInteger}${decimalPoint}${significandFractional}${exponentDelimiter}${exponent}`; | ||||
}; | }; | ||||
/** | |||||
* Converts a string in exponential notation (e.g. 1e+2) to a number string (e.g. 100). | |||||
* @param exp - The number in exponential notation to convert. | |||||
* @param options - Options to use when converting number strings. | |||||
* @returns string The number string. | |||||
*/ | |||||
export const exponentialToNumberString = ( | export const exponentialToNumberString = ( | ||||
exp: string, | exp: string, | ||||
options = {} as ExponentialToNumberStringOptions, | options = {} as ExponentialToNumberStringOptions, | ||||
@@ -2,7 +2,7 @@ import { | |||||
bigIntMax, bigIntMin, | bigIntMax, bigIntMin, | ||||
Group, | Group, | ||||
GROUP_DIGITS_INDEX, | GROUP_DIGITS_INDEX, | ||||
GROUP_PLACE_INDEX, | |||||
GROUP_PLACE_INDEX, GroupDigits, | |||||
InvalidTokenError, | InvalidTokenError, | ||||
} from '../../../common'; | } from '../../../common'; | ||||
import { | import { | ||||
@@ -269,14 +269,14 @@ const parseThousand = (acc: ParserState, token: string): ParserState => { | |||||
if (acc.mode === ParseGroupsMode.ONES_MODE) { | if (acc.mode === ParseGroupsMode.ONES_MODE) { | ||||
const ones = ONES.findIndex((o) => o === acc.lastToken); | const ones = ONES.findIndex((o) => o === acc.lastToken); | ||||
if (ones > -1) { | if (ones > -1) { | ||||
lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 2)}${ones}`; | |||||
lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 2)}${ones}` as GroupDigits; | |||||
} | } | ||||
} else if (acc.mode === ParseGroupsMode.TENS_MODE) { | } else if (acc.mode === ParseGroupsMode.TENS_MODE) { | ||||
const tens = TENS.findIndex((t) => t === acc.lastToken); | const tens = TENS.findIndex((t) => t === acc.lastToken); | ||||
if (tens > -1) { | if (tens > -1) { | ||||
lastGroup[GROUP_DIGITS_INDEX] = ( | lastGroup[GROUP_DIGITS_INDEX] = ( | ||||
`${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}${tens}${lastGroup[GROUP_DIGITS_INDEX].slice(2)}` | `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}${tens}${lastGroup[GROUP_DIGITS_INDEX].slice(2)}` | ||||
); | |||||
) as GroupDigits; | |||||
} | } | ||||
} | } | ||||
@@ -294,7 +294,7 @@ const parseThousand = (acc: ParserState, token: string): ParserState => { | |||||
const parseHundred = (acc: ParserState): ParserState => { | const parseHundred = (acc: ParserState): ParserState => { | ||||
const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; | const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; | ||||
const hundreds = ONES.findIndex((o) => o === acc.lastToken); | const hundreds = ONES.findIndex((o) => o === acc.lastToken); | ||||
lastGroup[GROUP_DIGITS_INDEX] = `${hundreds}${lastGroup[GROUP_DIGITS_INDEX].slice(1)}`; | |||||
lastGroup[GROUP_DIGITS_INDEX] = `${hundreds}${lastGroup[GROUP_DIGITS_INDEX].slice(1)}` as GroupDigits; | |||||
return { | return { | ||||
...acc, | ...acc, | ||||
groups: [...acc.groups.slice(0, -1), lastGroup], | groups: [...acc.groups.slice(0, -1), lastGroup], | ||||
@@ -308,7 +308,7 @@ const parseFinal = (acc: ParserState): ParserState => { | |||||
if (acc.mode === ParseGroupsMode.ONES_MODE) { | if (acc.mode === ParseGroupsMode.ONES_MODE) { | ||||
const ones = ONES.findIndex((o) => o === acc.lastToken); | const ones = ONES.findIndex((o) => o === acc.lastToken); | ||||
if (ones > -1) { | if (ones > -1) { | ||||
lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 2)}${ones}`; | |||||
lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 2)}${ones}` as GroupDigits; | |||||
} | } | ||||
// We assume last token without parsed place will always be the smallest | // We assume last token without parsed place will always be the smallest | ||||
lastGroup[GROUP_PLACE_INDEX] = BigInt(0); | lastGroup[GROUP_PLACE_INDEX] = BigInt(0); | ||||
@@ -322,7 +322,9 @@ const parseFinal = (acc: ParserState): ParserState => { | |||||
if (acc.mode === ParseGroupsMode.TENS_MODE) { | if (acc.mode === ParseGroupsMode.TENS_MODE) { | ||||
const tens = TENS.findIndex((o) => o === acc.lastToken); | const tens = TENS.findIndex((o) => o === acc.lastToken); | ||||
if (tens > -1) { | if (tens > -1) { | ||||
lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[0].slice(0, 1)}${tens}${lastGroup[GROUP_DIGITS_INDEX].slice(2)}`; | |||||
lastGroup[GROUP_DIGITS_INDEX] = ( | |||||
`${lastGroup[0].slice(0, 1)}${tens}${lastGroup[GROUP_DIGITS_INDEX].slice(2)}` | |||||
) as GroupDigits; | |||||
} | } | ||||
lastGroup[GROUP_PLACE_INDEX] = BigInt(0); | lastGroup[GROUP_PLACE_INDEX] = BigInt(0); | ||||
return { | return { | ||||
@@ -360,11 +362,11 @@ const parseTenPlusOnes = (acc: ParserState, token: string): ParserState => { | |||||
...acc, | ...acc, | ||||
lastToken: token, | lastToken: token, | ||||
mode: ParseGroupsMode.TEN_PLUS_ONES_MODE, | mode: ParseGroupsMode.TEN_PLUS_ONES_MODE, | ||||
groups: [...acc.groups, [`01${tenPlusOnes}`, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]], | |||||
groups: [...acc.groups, [`01${tenPlusOnes}` as GroupDigits, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]], | |||||
}; | }; | ||||
} | } | ||||
lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}1${tenPlusOnes}`; | |||||
lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}1${tenPlusOnes}` as GroupDigits; | |||||
return { | return { | ||||
...acc, | ...acc, | ||||
lastToken: token, | lastToken: token, | ||||
@@ -381,13 +383,14 @@ const parseTens = (acc: ParserState, token: string): ParserState => { | |||||
...acc, | ...acc, | ||||
lastToken: token, | lastToken: token, | ||||
mode: ParseGroupsMode.TENS_MODE, | mode: ParseGroupsMode.TENS_MODE, | ||||
groups: [...acc.groups, [`0${tens}0`, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]], | |||||
groups: [...acc.groups, [`0${tens}0` as GroupDigits, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]], | |||||
}; | }; | ||||
} | } | ||||
lastGroup[GROUP_DIGITS_INDEX] = ( | lastGroup[GROUP_DIGITS_INDEX] = ( | ||||
`${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}${tens}${lastGroup[GROUP_DIGITS_INDEX].slice(2)}` | `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}${tens}${lastGroup[GROUP_DIGITS_INDEX].slice(2)}` | ||||
); | |||||
) as GroupDigits; | |||||
return { | return { | ||||
...acc, | ...acc, | ||||
lastToken: token, | lastToken: token, | ||||
@@ -1,7 +1,7 @@ | |||||
import { | import { | ||||
Group, | Group, | ||||
GROUP_DIGITS_INDEX, | GROUP_DIGITS_INDEX, | ||||
GROUP_PLACE_INDEX, | |||||
GROUP_PLACE_INDEX, GroupDigits, | |||||
GroupPlace, | GroupPlace, | ||||
} from '../../../common'; | } from '../../../common'; | ||||
import { numberToExponential } from '../../../exponent'; | import { numberToExponential } from '../../../exponent'; | ||||
@@ -179,16 +179,16 @@ const getGroupName = (place: GroupPlace, shortenMillia: boolean) => { | |||||
const currentPlace = BigInt(Math.floor((cc.length - i - 1) / 3)); | const currentPlace = BigInt(Math.floor((cc.length - i - 1) / 3)); | ||||
const newGroup = [EMPTY_GROUP_DIGITS, currentPlace] as Group; | const newGroup = [EMPTY_GROUP_DIGITS, currentPlace] as Group; | ||||
if (typeof firstGroup === 'undefined') { | if (typeof firstGroup === 'undefined') { | ||||
newGroup[GROUP_DIGITS_INDEX] = c; | |||||
newGroup[GROUP_DIGITS_INDEX] = c as GroupDigits; | |||||
return [newGroup]; | return [newGroup]; | ||||
} | } | ||||
if (firstGroup[0].length > 2) { | if (firstGroup[0].length > 2) { | ||||
newGroup[GROUP_DIGITS_INDEX] = c; | |||||
newGroup[GROUP_DIGITS_INDEX] = c as GroupDigits; | |||||
return [newGroup, ...acc]; | return [newGroup, ...acc]; | ||||
} | } | ||||
newGroup[GROUP_DIGITS_INDEX] = c + firstGroup[0]; | |||||
newGroup[GROUP_DIGITS_INDEX] = (c + firstGroup[0]) as GroupDigits; | |||||
return [ | return [ | ||||
newGroup, | newGroup, | ||||
...acc.slice(1), | ...acc.slice(1), | ||||
@@ -309,11 +309,11 @@ export const splitIntoGroups = (value: string): Group[] => { | |||||
const lastGroupDigits = lastGroup[0].split(''); | const lastGroupDigits = lastGroup[0].split(''); | ||||
lastGroupDigits[currentPlaceInGroup] = c; | lastGroupDigits[currentPlaceInGroup] = c; | ||||
return [...acc.slice(0, -1) ?? [], [ | return [...acc.slice(0, -1) ?? [], [ | ||||
lastGroupDigits.join(''), | |||||
lastGroupDigits.join('') as GroupDigits, | |||||
currentPlace, | currentPlace, | ||||
]]; | ]]; | ||||
} | } | ||||
return [...acc, [c.padEnd(3, '0'), currentPlace]]; | |||||
return [...acc, [c.padEnd(3, '0') as GroupDigits, currentPlace]]; | |||||
}, | }, | ||||
[], | [], | ||||
); | ); | ||||
@@ -2,7 +2,7 @@ import { | |||||
bigIntMax, bigIntMin, | bigIntMax, bigIntMin, | ||||
Group, | Group, | ||||
GROUP_DIGITS_INDEX, | GROUP_DIGITS_INDEX, | ||||
GROUP_PLACE_INDEX, | |||||
GROUP_PLACE_INDEX, GroupDigits, | |||||
InvalidTokenError, | InvalidTokenError, | ||||
} from '../../../common'; | } from '../../../common'; | ||||
import { | import { | ||||
@@ -267,14 +267,14 @@ const parseThousand = (acc: ParserState, token: string): ParserState => { | |||||
if (acc.mode === ParseGroupsMode.ONES_MODE) { | if (acc.mode === ParseGroupsMode.ONES_MODE) { | ||||
const ones = ONES.findIndex((o) => o === acc.lastToken); | const ones = ONES.findIndex((o) => o === acc.lastToken); | ||||
if (ones > -1) { | if (ones > -1) { | ||||
lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 2)}${ones}`; | |||||
lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 2)}${ones}` as GroupDigits; | |||||
} | } | ||||
} else if (acc.mode === ParseGroupsMode.TENS_MODE) { | } else if (acc.mode === ParseGroupsMode.TENS_MODE) { | ||||
const tens = TENS.findIndex((t) => t === acc.lastToken); | const tens = TENS.findIndex((t) => t === acc.lastToken); | ||||
if (tens > -1) { | if (tens > -1) { | ||||
lastGroup[GROUP_DIGITS_INDEX] = ( | lastGroup[GROUP_DIGITS_INDEX] = ( | ||||
`${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}${tens}${lastGroup[GROUP_DIGITS_INDEX].slice(2)}` | `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}${tens}${lastGroup[GROUP_DIGITS_INDEX].slice(2)}` | ||||
); | |||||
) as GroupDigits; | |||||
} | } | ||||
} | } | ||||
@@ -292,7 +292,7 @@ const parseThousand = (acc: ParserState, token: string): ParserState => { | |||||
const parseHundred = (acc: ParserState): ParserState => { | const parseHundred = (acc: ParserState): ParserState => { | ||||
const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; | const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; | ||||
const hundreds = ONES.findIndex((o) => o === acc.lastToken); | const hundreds = ONES.findIndex((o) => o === acc.lastToken); | ||||
lastGroup[GROUP_DIGITS_INDEX] = `${hundreds}${lastGroup[GROUP_DIGITS_INDEX].slice(1)}`; | |||||
lastGroup[GROUP_DIGITS_INDEX] = `${hundreds}${lastGroup[GROUP_DIGITS_INDEX].slice(1)}` as GroupDigits; | |||||
return { | return { | ||||
...acc, | ...acc, | ||||
groups: [...acc.groups.slice(0, -1), lastGroup], | groups: [...acc.groups.slice(0, -1), lastGroup], | ||||
@@ -306,7 +306,7 @@ const parseFinal = (acc: ParserState): ParserState => { | |||||
if (acc.mode === ParseGroupsMode.ONES_MODE) { | if (acc.mode === ParseGroupsMode.ONES_MODE) { | ||||
const ones = ONES.findIndex((o) => o === acc.lastToken); | const ones = ONES.findIndex((o) => o === acc.lastToken); | ||||
if (ones > -1) { | if (ones > -1) { | ||||
lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 2)}${ones}`; | |||||
lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 2)}${ones}` as GroupDigits; | |||||
} | } | ||||
// We assume last token without parsed place will always be the smallest | // We assume last token without parsed place will always be the smallest | ||||
lastGroup[GROUP_PLACE_INDEX] = BigInt(0); | lastGroup[GROUP_PLACE_INDEX] = BigInt(0); | ||||
@@ -320,7 +320,9 @@ const parseFinal = (acc: ParserState): ParserState => { | |||||
if (acc.mode === ParseGroupsMode.TENS_MODE) { | if (acc.mode === ParseGroupsMode.TENS_MODE) { | ||||
const tens = TENS.findIndex((o) => o === acc.lastToken); | const tens = TENS.findIndex((o) => o === acc.lastToken); | ||||
if (tens > -1) { | if (tens > -1) { | ||||
lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[0].slice(0, 1)}${tens}${lastGroup[GROUP_DIGITS_INDEX].slice(2)}`; | |||||
lastGroup[GROUP_DIGITS_INDEX] = ( | |||||
`${lastGroup[0].slice(0, 1)}${tens}${lastGroup[GROUP_DIGITS_INDEX].slice(2)}` | |||||
) as GroupDigits; | |||||
} | } | ||||
lastGroup[GROUP_PLACE_INDEX] = BigInt(0); | lastGroup[GROUP_PLACE_INDEX] = BigInt(0); | ||||
return { | return { | ||||
@@ -358,11 +360,11 @@ const parseTenPlusOnes = (acc: ParserState, token: string): ParserState => { | |||||
...acc, | ...acc, | ||||
lastToken: token, | lastToken: token, | ||||
mode: ParseGroupsMode.TEN_PLUS_ONES_MODE, | mode: ParseGroupsMode.TEN_PLUS_ONES_MODE, | ||||
groups: [...acc.groups, [`01${tenPlusOnes}`, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]], | |||||
groups: [...acc.groups, [`01${tenPlusOnes}` as GroupDigits, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]], | |||||
}; | }; | ||||
} | } | ||||
lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}1${tenPlusOnes}`; | |||||
lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}1${tenPlusOnes}` as GroupDigits; | |||||
return { | return { | ||||
...acc, | ...acc, | ||||
lastToken: token, | lastToken: token, | ||||
@@ -379,13 +381,14 @@ const parseTens = (acc: ParserState, token: string): ParserState => { | |||||
...acc, | ...acc, | ||||
lastToken: token, | lastToken: token, | ||||
mode: ParseGroupsMode.TENS_MODE, | mode: ParseGroupsMode.TENS_MODE, | ||||
groups: [...acc.groups, [`0${tens}0`, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]], | |||||
groups: [...acc.groups, [`0${tens}0` as GroupDigits, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]], | |||||
}; | }; | ||||
} | } | ||||
lastGroup[GROUP_DIGITS_INDEX] = ( | lastGroup[GROUP_DIGITS_INDEX] = ( | ||||
`${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}${tens}${lastGroup[GROUP_DIGITS_INDEX].slice(2)}` | `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}${tens}${lastGroup[GROUP_DIGITS_INDEX].slice(2)}` | ||||
); | |||||
) as GroupDigits; | |||||
return { | return { | ||||
...acc, | ...acc, | ||||
lastToken: token, | lastToken: token, | ||||
@@ -1,7 +1,7 @@ | |||||
import { | import { | ||||
Group, | Group, | ||||
GROUP_DIGITS_INDEX, | GROUP_DIGITS_INDEX, | ||||
GROUP_PLACE_INDEX, | |||||
GROUP_PLACE_INDEX, GroupDigits, | |||||
GroupPlace, | GroupPlace, | ||||
} from '../../../common'; | } from '../../../common'; | ||||
import { numberToExponential } from '../../../exponent'; | import { numberToExponential } from '../../../exponent'; | ||||
@@ -177,16 +177,16 @@ const getGroupName = (place: GroupPlace, shortenMillia: boolean) => { | |||||
const currentPlace = BigInt(Math.floor((cc.length - i - 1) / 3)); | const currentPlace = BigInt(Math.floor((cc.length - i - 1) / 3)); | ||||
const newGroup = [EMPTY_GROUP_DIGITS, currentPlace] as Group; | const newGroup = [EMPTY_GROUP_DIGITS, currentPlace] as Group; | ||||
if (typeof firstGroup === 'undefined') { | if (typeof firstGroup === 'undefined') { | ||||
newGroup[GROUP_DIGITS_INDEX] = c; | |||||
newGroup[GROUP_DIGITS_INDEX] = c as GroupDigits; | |||||
return [newGroup]; | return [newGroup]; | ||||
} | } | ||||
if (firstGroup[0].length > 2) { | if (firstGroup[0].length > 2) { | ||||
newGroup[GROUP_DIGITS_INDEX] = c; | |||||
newGroup[GROUP_DIGITS_INDEX] = c as GroupDigits; | |||||
return [newGroup, ...acc]; | return [newGroup, ...acc]; | ||||
} | } | ||||
newGroup[GROUP_DIGITS_INDEX] = c + firstGroup[0]; | |||||
newGroup[GROUP_DIGITS_INDEX] = (c + firstGroup[0]) as GroupDigits; | |||||
return [ | return [ | ||||
newGroup, | newGroup, | ||||
...acc.slice(1), | ...acc.slice(1), | ||||
@@ -307,11 +307,11 @@ export const splitIntoGroups = (value: string): Group[] => { | |||||
const lastGroupDigits = lastGroup[0].split(''); | const lastGroupDigits = lastGroup[0].split(''); | ||||
lastGroupDigits[currentPlaceInGroup] = c; | lastGroupDigits[currentPlaceInGroup] = c; | ||||
return [...acc.slice(0, -1), [ | return [...acc.slice(0, -1), [ | ||||
lastGroupDigits.join(''), | |||||
lastGroupDigits.join('') as GroupDigits, | |||||
currentPlace, | currentPlace, | ||||
]]; | ]]; | ||||
} | } | ||||
return [...acc, [c.padEnd(3, '0'), currentPlace]]; | |||||
return [...acc, [c.padEnd(3, '0') as GroupDigits, currentPlace]]; | |||||
}, | }, | ||||
[], | [], | ||||
); | ); | ||||