diff --git a/packages/core/src/common.ts b/packages/core/src/common.ts index a06882f..6cf8c41 100644 --- a/packages/core/src/common.ts +++ b/packages/core/src/common.ts @@ -6,7 +6,7 @@ type GroupDigits = string; /** * Group place. */ -type GroupPlace = number; +export type GroupPlace = bigint; /** * Group of digits and its place. diff --git a/packages/core/src/exponent.ts b/packages/core/src/exponent.ts index 3d235e9..654a791 100644 --- a/packages/core/src/exponent.ts +++ b/packages/core/src/exponent.ts @@ -81,7 +81,7 @@ export class InvalidValueTypeError extends TypeError { * @param value - The value to force a sign on. * @returns string The value with a sign. */ -const forceNumberSign = (value: number | bigint) => { +const forceNumberSign = (value: bigint) => { const isExponentNegative = value < 0; const exponentValueAbs = isExponentNegative ? -value : value; const exponentSign = isExponentNegative ? DEFAULT_NEGATIVE_SYMBOL : DEFAULT_POSITIVE_SYMBOL; diff --git a/packages/core/src/systems/en-US/common.ts b/packages/core/src/systems/en-US/common.ts index bff1878..f8da2b2 100644 --- a/packages/core/src/systems/en-US/common.ts +++ b/packages/core/src/systems/en-US/common.ts @@ -19,7 +19,7 @@ export const EXPONENT_DELIMITER = 'e' as const; export const EMPTY_GROUP_DIGITS = '000' as const; -export const EMPTY_PLACE: Group = [EMPTY_GROUP_DIGITS, 0]; +export const EMPTY_PLACE: Group = [EMPTY_GROUP_DIGITS, BigInt(0)]; /** * Ones number names. diff --git a/packages/core/src/systems/en-US/parse.ts b/packages/core/src/systems/en-US/parse.ts index 884c679..2fd055f 100644 --- a/packages/core/src/systems/en-US/parse.ts +++ b/packages/core/src/systems/en-US/parse.ts @@ -155,14 +155,14 @@ const doParseGroupName = (result: DoParseState): DoParseState => { const getGroupPlaceFromGroupName = (groupName: string) => { if (groupName === THOUSAND) { - return 1; + return BigInt(1); } const groupNameBase = groupName.replace(ILLION_SUFFIX, ''); const specialMillions = MILLIONS_SPECIAL_PREFIXES.findIndex((p) => groupNameBase === p); if (specialMillions > -1) { - return specialMillions + 1; + return BigInt(specialMillions + 1); } let result: DoParseState = { @@ -176,14 +176,14 @@ const getGroupPlaceFromGroupName = (groupName: string) => { result = doParseGroupName(result); } while (!result.done); - const bigGroupPlace = Number( + const bigGroupPlace = BigInt( result.millias .map((s) => s.toString().padStart(3, '0')) .reverse() .join(''), ); - return bigGroupPlace + 1; + return bigGroupPlace + BigInt(1); }; enum ParseGroupsMode { @@ -250,7 +250,7 @@ const parseFinal = (acc: ParserState): ParserState => { 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] = 0; + lastGroup[GROUP_PLACE_INDEX] = BigInt(0); return { ...acc, groups: [...acc.groups.slice(0, -1), lastGroup], @@ -263,7 +263,7 @@ const parseFinal = (acc: ParserState): ParserState => { if (tens > -1) { lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[0].slice(0, 1)}${tens}${lastGroup[GROUP_DIGITS_INDEX].slice(2)}`; } - lastGroup[GROUP_PLACE_INDEX] = 0; + lastGroup[GROUP_PLACE_INDEX] = BigInt(0); return { ...acc, groups: [...acc.groups.slice(0, -1), lastGroup], @@ -299,7 +299,7 @@ const parseTenPlusOnes = (acc: ParserState, token: string): ParserState => { ...acc, lastToken: token, mode: ParseGroupsMode.TEN_PLUS_ONES_MODE, - groups: [...acc.groups, [`01${tenPlusOnes}`, lastGroup[GROUP_PLACE_INDEX] - 1]], + groups: [...acc.groups, [`01${tenPlusOnes}`, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]], }; } @@ -320,7 +320,7 @@ const parseTens = (acc: ParserState, token: string): ParserState => { ...acc, lastToken: token, mode: ParseGroupsMode.TENS_MODE, - groups: [...acc.groups, [`0${tens}0`, lastGroup[GROUP_PLACE_INDEX] - 1]], + groups: [...acc.groups, [`0${tens}0`, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]], }; } @@ -379,14 +379,37 @@ 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, +); + export const combineGroups = (groups: Group[]) => { const places = groups.map((g) => g[GROUP_PLACE_INDEX]); - const maxPlace = Math.max(...places); - const minPlace = Math.min(...places); + 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 -= 1) { + 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); } @@ -401,7 +424,9 @@ export const combineGroups = (groups: Group[]) => { const firstGroupDigits = firstGroup[0]; const firstGroupDigitsWithoutZeroes = firstGroupDigits.replace(/^0+/, ''); const exponentExtra = firstGroupDigits.length - firstGroupDigitsWithoutZeroes.length; - const exponentValue = BigInt((firstGroupPlace * 3) + (2 - exponentExtra)); + 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; diff --git a/packages/core/src/systems/en-US/stringify.ts b/packages/core/src/systems/en-US/stringify.ts index e79d906..2c421df 100644 --- a/packages/core/src/systems/en-US/stringify.ts +++ b/packages/core/src/systems/en-US/stringify.ts @@ -1,4 +1,9 @@ -import { Group, GROUP_DIGITS_INDEX, GROUP_PLACE_INDEX } from '../../common'; +import { + Group, + GROUP_DIGITS_INDEX, + GROUP_PLACE_INDEX, + GroupPlace, +} from '../../common'; import { numberToExponential } from '../../exponent'; import { CENTILLIONS_PREFIXES, @@ -74,7 +79,7 @@ const makeHundredsName = (hundreds: number, tens: number, ones: number) => { * @param milliaCount - Number of millia- groups. * @returns string The millions prefix. */ -const makeMillionsPrefix = (millions: number, milliaCount: number) => { +const makeMillionsPrefix = (millions: number, milliaCount: GroupPlace) => { if (milliaCount > 0) { return MILLIONS_PREFIXES[millions] as MillionsPrefix; } @@ -89,7 +94,7 @@ const makeMillionsPrefix = (millions: number, milliaCount: number) => { * @param milliaCount - Number of millia- groups. * @returns string The decillions prefix. */ -const makeDecillionsPrefix = (decillions: number, millions: number, milliaCount: number) => { +const makeDecillionsPrefix = (decillions: number, millions: number, milliaCount: GroupPlace) => { if (decillions === 0) { return makeMillionsPrefix(millions, milliaCount); } @@ -111,7 +116,7 @@ const makeCentillionsPrefix = ( centillions: number, decillions: number, millions: number, - milliaCount: number, + milliaCount: GroupPlace, ) => { if (centillions === 0) { return makeDecillionsPrefix(decillions, millions, milliaCount); @@ -123,23 +128,31 @@ const makeCentillionsPrefix = ( return `${hundredsName}${onesPrefix}${decillions > 0 ? tensName : ''}` as const; }; -const getGroupName = (place: number, shortenMillia: boolean) => { - if (place === 0) { +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 === 1) { + if (place === BigInt(1)) { return THOUSAND; } - const bigGroupPlace = place - 1; + const bigGroupPlace = place - BigInt(1); const groupGroups = bigGroupPlace .toString() .split('') .reduceRight( (acc, c, i, cc) => { const firstGroup = acc.at(0); - const currentPlace = 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; if (typeof firstGroup === 'undefined') { newGroup[GROUP_DIGITS_INDEX] = c; @@ -170,7 +183,7 @@ const getGroupName = (place: number, shortenMillia: boolean) => { const milliaSuffix = ( shortenMillia && groupPlace > 1 ? `${MILLIA_PREFIX}${SHORT_MILLIA_DELIMITER}${groupPlace}` - : MILLIA_PREFIX.repeat(groupPlace) + : repeatString(MILLIA_PREFIX, groupPlace) ); if (groupDigits === '001') { @@ -194,7 +207,7 @@ const getGroupName = (place: number, shortenMillia: boolean) => { export const makeGroups = (groups: Group[], options?: Record): string[] => { const filteredGroups = groups.filter(([digits, place]) => ( - place === 0 || digits !== EMPTY_GROUP_DIGITS + place === BigInt(0) || digits !== EMPTY_GROUP_DIGITS )); return filteredGroups.map( @@ -232,7 +245,7 @@ export const group = (value: string): Group[] => { const significantDigits = significand.replace(DECIMAL_POINT, ''); return significantDigits.split('').reduce( (acc, c, i) => { - const currentPlace = Math.floor((exponent - i) / 3); + 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) {