Browse Source

Switch place values to bigint

Use bigint for place values to be more adaptable with longer inputs.
master
TheoryOfNekomata 1 year ago
parent
commit
9f9a055f03
5 changed files with 65 additions and 27 deletions
  1. +1
    -1
      packages/core/src/common.ts
  2. +1
    -1
      packages/core/src/exponent.ts
  3. +1
    -1
      packages/core/src/systems/en-US/common.ts
  4. +37
    -12
      packages/core/src/systems/en-US/parse.ts
  5. +25
    -12
      packages/core/src/systems/en-US/stringify.ts

+ 1
- 1
packages/core/src/common.ts View File

@@ -6,7 +6,7 @@ type GroupDigits = string;
/** /**
* Group place. * Group place.
*/ */
type GroupPlace = number;
export type GroupPlace = bigint;


/** /**
* Group of digits and its place. * Group of digits and its place.


+ 1
- 1
packages/core/src/exponent.ts View File

@@ -81,7 +81,7 @@ export class InvalidValueTypeError extends TypeError {
* @param value - The value to force a sign on. * @param value - The value to force a sign on.
* @returns string The value with a sign. * @returns string The value with a sign.
*/ */
const forceNumberSign = (value: number | bigint) => {
const forceNumberSign = (value: bigint) => {
const isExponentNegative = value < 0; const isExponentNegative = value < 0;
const exponentValueAbs = isExponentNegative ? -value : value; const exponentValueAbs = isExponentNegative ? -value : value;
const exponentSign = isExponentNegative ? DEFAULT_NEGATIVE_SYMBOL : DEFAULT_POSITIVE_SYMBOL; const exponentSign = isExponentNegative ? DEFAULT_NEGATIVE_SYMBOL : DEFAULT_POSITIVE_SYMBOL;


+ 1
- 1
packages/core/src/systems/en-US/common.ts View File

@@ -19,7 +19,7 @@ export const EXPONENT_DELIMITER = 'e' as const;


export const EMPTY_GROUP_DIGITS = '000' 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. * Ones number names.


+ 37
- 12
packages/core/src/systems/en-US/parse.ts View File

@@ -155,14 +155,14 @@ const doParseGroupName = (result: DoParseState): DoParseState => {


const getGroupPlaceFromGroupName = (groupName: string) => { const getGroupPlaceFromGroupName = (groupName: string) => {
if (groupName === THOUSAND) { if (groupName === THOUSAND) {
return 1;
return BigInt(1);
} }


const groupNameBase = groupName.replace(ILLION_SUFFIX, ''); const groupNameBase = groupName.replace(ILLION_SUFFIX, '');
const specialMillions = MILLIONS_SPECIAL_PREFIXES.findIndex((p) => groupNameBase === p); const specialMillions = MILLIONS_SPECIAL_PREFIXES.findIndex((p) => groupNameBase === p);


if (specialMillions > -1) { if (specialMillions > -1) {
return specialMillions + 1;
return BigInt(specialMillions + 1);
} }


let result: DoParseState = { let result: DoParseState = {
@@ -176,14 +176,14 @@ const getGroupPlaceFromGroupName = (groupName: string) => {
result = doParseGroupName(result); result = doParseGroupName(result);
} while (!result.done); } while (!result.done);


const bigGroupPlace = Number(
const bigGroupPlace = BigInt(
result.millias result.millias
.map((s) => s.toString().padStart(3, '0')) .map((s) => s.toString().padStart(3, '0'))
.reverse() .reverse()
.join(''), .join(''),
); );


return bigGroupPlace + 1;
return bigGroupPlace + BigInt(1);
}; };


enum ParseGroupsMode { enum ParseGroupsMode {
@@ -250,7 +250,7 @@ const parseFinal = (acc: ParserState): ParserState => {
lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 2)}${ones}`; lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 2)}${ones}`;
} }
// 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] = 0;
lastGroup[GROUP_PLACE_INDEX] = BigInt(0);
return { return {
...acc, ...acc,
groups: [...acc.groups.slice(0, -1), lastGroup], groups: [...acc.groups.slice(0, -1), lastGroup],
@@ -263,7 +263,7 @@ const parseFinal = (acc: ParserState): ParserState => {
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)}`;
} }
lastGroup[GROUP_PLACE_INDEX] = 0;
lastGroup[GROUP_PLACE_INDEX] = BigInt(0);
return { return {
...acc, ...acc,
groups: [...acc.groups.slice(0, -1), lastGroup], groups: [...acc.groups.slice(0, -1), lastGroup],
@@ -299,7 +299,7 @@ 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] - 1]],
groups: [...acc.groups, [`01${tenPlusOnes}`, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]],
}; };
} }


@@ -320,7 +320,7 @@ 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] - 1]],
groups: [...acc.groups, [`0${tens}0`, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]],
}; };
} }


@@ -379,14 +379,37 @@ export const parseGroups = (tokens: string[]) => {
return groups; return groups;
}; };


const bigIntMax = (...b: bigint[]) => b.reduce(
(previousMax, current) => {
if (typeof previousMax === 'undefined') {
return current;
}
return previousMax > current ? previousMax : current;
},
undefined as bigint | undefined,
);

const bigIntMin = (...b: bigint[]) => b.reduce(
(previousMin, current) => {
if (typeof previousMin === 'undefined') {
return current;
}
return previousMin < current ? previousMin : current;
},
undefined as bigint | undefined,
);

export const combineGroups = (groups: Group[]) => { export const combineGroups = (groups: Group[]) => {
const places = groups.map((g) => g[GROUP_PLACE_INDEX]); 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 firstGroup = groups.find((g) => g[GROUP_PLACE_INDEX] === maxPlace) ?? [...EMPTY_PLACE];
const firstGroupPlace = firstGroup[GROUP_PLACE_INDEX]; const firstGroupPlace = firstGroup[GROUP_PLACE_INDEX];
const groupsSorted = []; 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]; const thisGroup = groups.find((g) => g[GROUP_PLACE_INDEX] === i) ?? [EMPTY_GROUP_DIGITS, i];
groupsSorted.push(thisGroup); groupsSorted.push(thisGroup);
} }
@@ -401,7 +424,9 @@ export const combineGroups = (groups: Group[]) => {
const firstGroupDigits = firstGroup[0]; const firstGroupDigits = firstGroup[0];
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(
(BigInt(firstGroupPlace) * BigInt(3)) + (BigInt(2) - BigInt(exponentExtra)),
);
const isExponentNegative = exponentValue < 0; const isExponentNegative = exponentValue < 0;
const exponentValueAbs = isExponentNegative ? -exponentValue : exponentValue; const exponentValueAbs = isExponentNegative ? -exponentValue : exponentValue;
const exponentSign = isExponentNegative ? NEGATIVE_SYMBOL : POSITIVE_SYMBOL; const exponentSign = isExponentNegative ? NEGATIVE_SYMBOL : POSITIVE_SYMBOL;


+ 25
- 12
packages/core/src/systems/en-US/stringify.ts View File

@@ -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 { numberToExponential } from '../../exponent';
import { import {
CENTILLIONS_PREFIXES, CENTILLIONS_PREFIXES,
@@ -74,7 +79,7 @@ const makeHundredsName = (hundreds: number, tens: number, ones: number) => {
* @param milliaCount - Number of millia- groups. * @param milliaCount - Number of millia- groups.
* @returns string The millions prefix. * @returns string The millions prefix.
*/ */
const makeMillionsPrefix = (millions: number, milliaCount: number) => {
const makeMillionsPrefix = (millions: number, milliaCount: GroupPlace) => {
if (milliaCount > 0) { if (milliaCount > 0) {
return MILLIONS_PREFIXES[millions] as MillionsPrefix; return MILLIONS_PREFIXES[millions] as MillionsPrefix;
} }
@@ -89,7 +94,7 @@ const makeMillionsPrefix = (millions: number, milliaCount: number) => {
* @param milliaCount - Number of millia- groups. * @param milliaCount - Number of millia- groups.
* @returns string The decillions prefix. * @returns string The decillions prefix.
*/ */
const makeDecillionsPrefix = (decillions: number, millions: number, milliaCount: number) => {
const makeDecillionsPrefix = (decillions: number, millions: number, milliaCount: GroupPlace) => {
if (decillions === 0) { if (decillions === 0) {
return makeMillionsPrefix(millions, milliaCount); return makeMillionsPrefix(millions, milliaCount);
} }
@@ -111,7 +116,7 @@ const makeCentillionsPrefix = (
centillions: number, centillions: number,
decillions: number, decillions: number,
millions: number, millions: number,
milliaCount: number,
milliaCount: GroupPlace,
) => { ) => {
if (centillions === 0) { if (centillions === 0) {
return makeDecillionsPrefix(decillions, millions, milliaCount); return makeDecillionsPrefix(decillions, millions, milliaCount);
@@ -123,23 +128,31 @@ const makeCentillionsPrefix = (
return `${hundredsName}${onesPrefix}${decillions > 0 ? tensName : ''}` as const; 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; return '' as const;
} }


if (place === 1) {
if (place === BigInt(1)) {
return THOUSAND; return THOUSAND;
} }


const bigGroupPlace = place - 1;
const bigGroupPlace = place - BigInt(1);
const groupGroups = bigGroupPlace const groupGroups = bigGroupPlace
.toString() .toString()
.split('') .split('')
.reduceRight<Group[]>( .reduceRight<Group[]>(
(acc, c, i, cc) => { (acc, c, i, cc) => {
const firstGroup = acc.at(0); 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; 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;
@@ -170,7 +183,7 @@ const getGroupName = (place: number, shortenMillia: boolean) => {
const milliaSuffix = ( const milliaSuffix = (
shortenMillia && groupPlace > 1 shortenMillia && groupPlace > 1
? `${MILLIA_PREFIX}${SHORT_MILLIA_DELIMITER}${groupPlace}` ? `${MILLIA_PREFIX}${SHORT_MILLIA_DELIMITER}${groupPlace}`
: MILLIA_PREFIX.repeat(groupPlace)
: repeatString(MILLIA_PREFIX, groupPlace)
); );


if (groupDigits === '001') { if (groupDigits === '001') {
@@ -194,7 +207,7 @@ const getGroupName = (place: number, shortenMillia: boolean) => {


export const makeGroups = (groups: Group[], options?: Record<string, unknown>): string[] => { export const makeGroups = (groups: Group[], options?: Record<string, unknown>): string[] => {
const filteredGroups = groups.filter(([digits, place]) => ( const filteredGroups = groups.filter(([digits, place]) => (
place === 0 || digits !== EMPTY_GROUP_DIGITS
place === BigInt(0) || digits !== EMPTY_GROUP_DIGITS
)); ));


return filteredGroups.map( return filteredGroups.map(
@@ -232,7 +245,7 @@ export const group = (value: string): Group[] => {
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 = BigInt(Math.floor((exponent - i) / 3));
const lastGroup = acc.at(-1) ?? [EMPTY_GROUP_DIGITS, currentPlace]; const lastGroup = acc.at(-1) ?? [EMPTY_GROUP_DIGITS, currentPlace];
const currentPlaceInGroup = 2 - ((exponent - i) % 3); const currentPlaceInGroup = 2 - ((exponent - i) % 3);
if (lastGroup[GROUP_PLACE_INDEX] === currentPlace) { if (lastGroup[GROUP_PLACE_INDEX] === currentPlace) {


Loading…
Cancel
Save