@@ -3,7 +3,8 @@ | |||
"rules": { | |||
"indent": ["error", "tab"], | |||
"no-tabs": "off", | |||
"no-continue": "off" | |||
"no-continue": "off", | |||
"max-classes-per-file": "off" | |||
}, | |||
"extends": [ | |||
"lxsmnsyc/typescript" | |||
@@ -50,7 +50,7 @@ export interface StringifySystem { | |||
* @param place - The group place. | |||
* @param options - Options to use when creating the group. | |||
*/ | |||
makeGroup: (group: string, place?: GroupPlace, options?: Record<string, unknown>) => string; | |||
makeGroups: (groups: Group[], options?: Record<string, unknown>) => string[]; | |||
/** | |||
* Groups a string. | |||
* @param value - The string to group. | |||
@@ -45,7 +45,7 @@ export interface StringifyOptions { | |||
* Converts a numeric value to its name. | |||
* @param value - The value to convert. | |||
* @param options - Options to use when converting a value to its name. | |||
* @returns The name of the value. | |||
* @returns string The name of the value. | |||
*/ | |||
export const stringify = ( | |||
value: AllowedValue, | |||
@@ -65,13 +65,9 @@ export const stringify = ( | |||
return system.makeNegative(stringify(valueStr.slice(NEGATIVE_SYMBOL.length), options)); | |||
} | |||
const groups = system | |||
.group(valueStr) | |||
.map(([group, place]) => ( | |||
system.makeGroup(group, place, makeGroupOptions) | |||
)); | |||
return system.finalize(groups); | |||
const groups = system.group(valueStr); | |||
const groupNames = system.makeGroups(groups, makeGroupOptions); | |||
return system.finalize(groupNames); | |||
}; | |||
/** | |||
@@ -94,7 +90,7 @@ export interface ParseOptions { | |||
* Parses a name of a number to its numeric equivalent. | |||
* @param value - The value to parse. | |||
* @param options - Options to use when parsing a name of a number to its numeric equivalent. | |||
* @returns The numeric equivalent of the value. | |||
* @returns AllowedValue The numeric equivalent of the value. | |||
*/ | |||
export const parse = (value: string, options = {} as ParseOptions) => { | |||
const { system = enUS, type = 'string' } = options; | |||
@@ -50,7 +50,7 @@ const DEFAULT_NEGATIVE_SYMBOL = '-' as const; | |||
* Forces a value to have a decimal point. | |||
* @param value - The value to force a decimal point on. | |||
* @param decimalPoint - The decimal point character to use. | |||
* @returns The value with a decimal point. | |||
* @returns string The value with a decimal point. | |||
*/ | |||
const forceDecimalPoint = (value: string, decimalPoint: string) => ( | |||
value.includes(decimalPoint) | |||
@@ -79,7 +79,7 @@ export class InvalidValueTypeError extends TypeError { | |||
/** | |||
* Forces a number to have a sign. | |||
* @param value - The value to force a sign on. | |||
* @returns The value with a sign. | |||
* @returns string The value with a sign. | |||
*/ | |||
const forceNumberSign = (value: number | bigint) => { | |||
const isExponentNegative = value < 0; | |||
@@ -88,16 +88,22 @@ const forceNumberSign = (value: number | bigint) => { | |||
return `${exponentSign}${exponentValueAbs}`; | |||
}; | |||
interface ExponentialComponents { | |||
integer: string; | |||
fractional: string; | |||
exponent: string; | |||
} | |||
/** | |||
* Extracts the integer, fractional, and exponent components of a string in exponential notation. | |||
* @param value - The string value to extract components from. | |||
* @param options - Options to use when extracting components. | |||
* @returns The extracted components. | |||
* @returns ExponentialComponents The extracted components. | |||
*/ | |||
export const extractExponentialComponents = ( | |||
value: string, | |||
options = {} as NumberToExponentialOptions, | |||
) => { | |||
): ExponentialComponents => { | |||
const { | |||
decimalPoint = DEFAULT_DECIMAL_POINT, | |||
groupingSymbol = DEFAULT_GROUPING_SYMBOL, | |||
@@ -143,7 +149,7 @@ export const extractExponentialComponents = ( | |||
* Converts a numeric value to a string in exponential notation. Supports numbers of all types. | |||
* @param value - The value to convert. | |||
* @param options - Options to use when extracting components. | |||
* @returns The value in exponential notation. | |||
* @returns string The value in exponential notation. | |||
*/ | |||
export const numberToExponential = ( | |||
value: ValidValue, | |||
@@ -175,7 +175,7 @@ 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. | |||
* @returns string The name for the number. | |||
*/ | |||
const makeTensName = (tens: number, ones: number) => { | |||
if (tens === 0) { | |||
@@ -198,7 +198,7 @@ const makeTensName = (tens: number, ones: number) => { | |||
* @param hundreds - Hundreds digit. | |||
* @param tens - Tens digit. | |||
* @param ones - Ones digit. | |||
* @returns The name for the number. | |||
* @returns string The name for the number. | |||
*/ | |||
const makeHundredsName = (hundreds: number, tens: number, ones: number) => { | |||
if (hundreds === 0) { | |||
@@ -216,7 +216,7 @@ const makeHundredsName = (hundreds: number, tens: number, ones: number) => { | |||
* Builds a name for numbers in the millions. | |||
* @param millions - Millions digit. | |||
* @param milliaCount - Number of millia- groups. | |||
* @returns The millions prefix. | |||
* @returns string The millions prefix. | |||
*/ | |||
const makeMillionsPrefix = (millions: number, milliaCount: number) => { | |||
if (milliaCount > 0) { | |||
@@ -231,7 +231,7 @@ const makeMillionsPrefix = (millions: number, milliaCount: number) => { | |||
* @param decillions - Decillions digit. | |||
* @param millions - Millions digit. | |||
* @param milliaCount - Number of millia- groups. | |||
* @returns The decillions prefix. | |||
* @returns string The decillions prefix. | |||
*/ | |||
const makeDecillionsPrefix = (decillions: number, millions: number, milliaCount: number) => { | |||
if (decillions === 0) { | |||
@@ -249,7 +249,7 @@ const makeDecillionsPrefix = (decillions: number, millions: number, milliaCount: | |||
* @param decillions - Decillions digit. | |||
* @param millions - Millions digit. | |||
* @param milliaCount - Number of millia- groups. | |||
* @returns The centillions prefix. | |||
* @returns string The centillions prefix. | |||
*/ | |||
const makeCentillionsPrefix = ( | |||
centillions: number, | |||
@@ -332,22 +332,26 @@ const getGroupName = (place: number, shortenMillia: boolean) => { | |||
return `${groupGroups}${ILLION_SUFFIX}` as const; | |||
}; | |||
export const makeGroup = ( | |||
group: string, | |||
place: number, | |||
options?: Record<string, unknown>, | |||
): string => { | |||
const makeHundredsArgs = group | |||
.padStart(3, '0') | |||
.split('') | |||
.map((s) => Number(s)) as [number, number, number]; | |||
export const makeGroups = (groups: Group[], options?: Record<string, unknown>): string[] => { | |||
const filteredGroups = groups.filter(([digits, place]) => ( | |||
place === 0 || digits !== EMPTY_GROUP_DIGITS | |||
)); | |||
const groupDigitsName = makeHundredsName(...makeHundredsArgs); | |||
const groupName = getGroupName(place, options?.shortenMillia as boolean ?? false); | |||
if (groupName.length > 0) { | |||
return `${groupDigitsName} ${groupName}` as const; | |||
} | |||
return groupDigitsName; | |||
return filteredGroups.map( | |||
([group, place]) => { | |||
const makeHundredsArgs = group | |||
.padStart(3, '0') | |||
.split('') | |||
.map((s) => Number(s)) as [number, number, number]; | |||
const groupDigitsName = makeHundredsName(...makeHundredsArgs); | |||
const groupName = getGroupName(place, options?.shortenMillia as boolean ?? false); | |||
if (groupName.length > 0) { | |||
return `${groupDigitsName} ${groupName}`; | |||
} | |||
return groupDigitsName; | |||
}, | |||
); | |||
}; | |||
/** | |||
@@ -685,9 +689,17 @@ export const parseGroups = (tokens: string[]) => { | |||
}; | |||
export const combineGroups = (groups: Group[]) => { | |||
const groupsSorted = groups.sort((a, b) => b[1] - a[1]); // sort by place | |||
const firstGroup = groupsSorted[0]; | |||
const places = groups.map((g) => g[1]); | |||
const maxPlace = Math.max(...places); | |||
const minPlace = Math.min(...places); | |||
const firstGroup = groups.find((g) => g[1] === maxPlace) ?? [...EMPTY_PLACE]; | |||
const firstGroupPlace = firstGroup[1]; | |||
const groupsSorted = []; | |||
for (let i = maxPlace; i >= minPlace; i -= 1) { | |||
const thisGroup = groups.find((g) => g[1] === i) ?? [...EMPTY_PLACE]; | |||
groupsSorted.push(thisGroup); | |||
} | |||
const digits = groupsSorted.reduce( | |||
(previousDigits, thisGroup) => { | |||
const [groupDigits] = thisGroup; | |||
@@ -1,5 +1,5 @@ | |||
import { describe, it, expect } from 'vitest'; | |||
import {parse, stringify, systems} from '../../src'; | |||
import { parse, stringify, systems } from '../../src'; | |||
import { numberToExponential } from '../../src/exponent'; | |||
const options = { system: systems.enUS }; | |||
@@ -19,8 +19,8 @@ describe('numerica', () => { | |||
${7} | ${'seven'} | |||
${8} | ${'eight'} | |||
${9} | ${'nine'} | |||
`('converts $ones to $expected', ({ ones, expected }) => { | |||
expect(stringify(ones, options)).toBe(expected) | |||
`('converts $ones to $expected', ({ ones, expected }: { ones: number, expected: string }) => { | |||
expect(stringify(ones, options)).toBe(expected); | |||
expect(parse(expected, { ...options, type: 'number' })).toBe(ones); | |||
}); | |||
}); | |||
@@ -38,8 +38,8 @@ describe('numerica', () => { | |||
${17} | ${'seventeen'} | |||
${18} | ${'eighteen'} | |||
${19} | ${'nineteen'} | |||
`('converts $tenPlusOnes to $expected', ({ tenPlusOnes, expected }) => { | |||
expect(stringify(tenPlusOnes, options)).toBe(expected) | |||
`('converts $tenPlusOnes to $expected', ({ tenPlusOnes, expected }: { tenPlusOnes: number, expected: string }) => { | |||
expect(stringify(tenPlusOnes, options)).toBe(expected); | |||
expect(parse(expected, { ...options, type: 'number' })).toBe(tenPlusOnes); | |||
}); | |||
}); | |||
@@ -56,10 +56,10 @@ describe('numerica', () => { | |||
${90} | ${99} | ${'ninety'} | |||
`('$tensStart-$tensEnd', ({ | |||
tensStart, tensBase, | |||
}) => { | |||
}: { tensStart: number, tensBase: string }) => { | |||
it.each` | |||
value | expected | |||
${tensStart + 0} | ${tensBase} | |||
${tensStart} | ${tensBase} | |||
${tensStart + 1} | ${`${tensBase} one`} | |||
${tensStart + 2} | ${`${tensBase} two`} | |||
${tensStart + 3} | ${`${tensBase} three`} | |||
@@ -69,7 +69,7 @@ describe('numerica', () => { | |||
${tensStart + 7} | ${`${tensBase} seven`} | |||
${tensStart + 8} | ${`${tensBase} eight`} | |||
${tensStart + 9} | ${`${tensBase} nine`} | |||
`('converts $value to $expected', ({ value, expected }) => { | |||
`('converts $value to $expected', ({ value, expected }: { value: number, expected: string }) => { | |||
expect(stringify(value, options)).toBe(expected); | |||
expect(parse(expected, { ...options, type: 'number' })).toBe(value); | |||
}); | |||
@@ -88,11 +88,11 @@ describe('numerica', () => { | |||
${900} | ${999} | ${'nine hundred'} | |||
`('$hundredsStart-$hundredsEnd', ({ | |||
hundredsStart, hundredsBase, | |||
}) => { | |||
}: { hundredsStart: number, hundredsBase: string }) => { | |||
describe(`${hundredsStart}-${hundredsStart + 9}`, () => { | |||
it.each` | |||
value | expected | |||
${hundredsStart + 0} | ${hundredsBase} | |||
${hundredsStart} | ${hundredsBase} | |||
${hundredsStart + 1} | ${`${hundredsBase} one`} | |||
${hundredsStart + 2} | ${`${hundredsBase} two`} | |||
${hundredsStart + 3} | ${`${hundredsBase} three`} | |||
@@ -102,9 +102,9 @@ describe('numerica', () => { | |||
${hundredsStart + 7} | ${`${hundredsBase} seven`} | |||
${hundredsStart + 8} | ${`${hundredsBase} eight`} | |||
${hundredsStart + 9} | ${`${hundredsBase} nine`} | |||
`('converts $value to $expected', ({ value, expected }) => { | |||
expect(stringify(value, options)).toBe(expected) | |||
expect(parse(expected, { ...options, type: 'number' })).toBe(value) | |||
`('converts $value to $expected', ({ value, expected }: { value: number, expected: string }) => { | |||
expect(stringify(value, options)).toBe(expected); | |||
expect(parse(expected, { ...options, type: 'number' })).toBe(value); | |||
}); | |||
}); | |||
@@ -121,9 +121,9 @@ describe('numerica', () => { | |||
${hundredsStart + 17} | ${`${hundredsBase} seventeen`} | |||
${hundredsStart + 18} | ${`${hundredsBase} eighteen`} | |||
${hundredsStart + 19} | ${`${hundredsBase} nineteen`} | |||
`('converts $value to $expected', ({ value, expected }) => { | |||
expect(stringify(value, options)).toBe(expected) | |||
expect(parse(expected, { ...options, type: 'number' })).toBe(value) | |||
`('converts $value to $expected', ({ value, expected }: { value: number, expected: string }) => { | |||
expect(stringify(value, options)).toBe(expected); | |||
expect(parse(expected, { ...options, type: 'number' })).toBe(value); | |||
}); | |||
}); | |||
@@ -139,10 +139,10 @@ describe('numerica', () => { | |||
${90} | ${99} | ${'ninety'} | |||
`('$start-$end', ({ | |||
start, base, | |||
}) => { | |||
}: { start: number, base: string }) => { | |||
it.each` | |||
value | expected | |||
${hundredsStart + start + 0} | ${`${hundredsBase} ${base}`} | |||
${hundredsStart + start} | ${`${hundredsBase} ${base}`} | |||
${hundredsStart + start + 1} | ${`${hundredsBase} ${base} one`} | |||
${hundredsStart + start + 2} | ${`${hundredsBase} ${base} two`} | |||
${hundredsStart + start + 3} | ${`${hundredsBase} ${base} three`} | |||
@@ -152,7 +152,7 @@ describe('numerica', () => { | |||
${hundredsStart + start + 7} | ${`${hundredsBase} ${base} seven`} | |||
${hundredsStart + start + 8} | ${`${hundredsBase} ${base} eight`} | |||
${hundredsStart + start + 9} | ${`${hundredsBase} ${base} nine`} | |||
`('converts $value to $expected', ({ value, expected }) => { | |||
`('converts $value to $expected', ({ value, expected }: { value: number, expected: string }) => { | |||
expect(stringify(value, options)).toBe(expected); | |||
expect(parse(expected, { ...options, type: 'number' })).toBe(value); | |||
}); | |||
@@ -191,7 +191,7 @@ describe('numerica', () => { | |||
${1e+24} | ${'one septillion'} | |||
${1e+27} | ${'one octillion'} | |||
${1e+30} | ${'one nonillion'} | |||
`('converts $value to $expected', ({ value, expected }) => { | |||
`('converts $value to $expected', ({ value, expected }: { value: number, expected: string }) => { | |||
expect(stringify(value, options)).toBe(expected); | |||
expect(parse(expected, { ...options, type: 'number' })).toBe(value); | |||
}); | |||
@@ -208,7 +208,7 @@ describe('numerica', () => { | |||
${'1e+54'} | ${'one septendecillion'} | |||
${'1e+57'} | ${'one octodecillion'} | |||
${'1e+60'} | ${'one novemdecillion'} | |||
`('converts $value to $expected', ({ value, expected }) => { | |||
`('converts $value to $expected', ({ value, expected }: { value: string, expected: string }) => { | |||
expect(stringify(value, options)).toBe(expected); | |||
expect(parse(expected, options)).toBe(value); | |||
}); | |||
@@ -225,7 +225,7 @@ describe('numerica', () => { | |||
${'1e+84'} | ${'one septenvigintillion'} | |||
${'1e+87'} | ${'one octovigintillion'} | |||
${'1e+90'} | ${'one novemvigintillion'} | |||
`('converts $value to $expected', ({ value, expected }) => { | |||
`('converts $value to $expected', ({ value, expected }: { value: string, expected: string }) => { | |||
expect(stringify(value, options)).toBe(expected); | |||
expect(parse(expected, options)).toBe(value); | |||
}); | |||
@@ -239,13 +239,13 @@ describe('numerica', () => { | |||
${'1e+213'} | ${'one septuagintillion'} | |||
${'1e+243'} | ${'one octogintillion'} | |||
${'1e+273'} | ${'one nonagintillion'} | |||
`('converts $value to $expected', ({ value, expected }) => { | |||
`('converts $value to $expected', ({ value, expected }: { value: string, expected: string }) => { | |||
expect(stringify(value, options)).toBe(expected); | |||
expect(parse(expected, options)).toBe(value); | |||
}); | |||
it.only('converts values', () => { | |||
const exp = numberToExponential('123,456,789,012,345,678,901,234,567,890'); | |||
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 | |||
@@ -257,6 +257,11 @@ describe('numerica', () => { | |||
// two hundred thirty four million | |||
// five hundred sixty seven thousand | |||
// eight hundred ninety | |||
expect(parse(stringify('123456789012345678901234567890'))).toBe(exp); | |||
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); | |||
}); | |||
}); |
@@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest'; | |||
import { stringify, parse } from '../../../src'; | |||
import { numberToExponential } from '../../../src/exponent'; | |||
describe.skip('Landon\'s original test cases', () => { | |||
describe('Landon\'s original test cases', () => { | |||
describe('Basic conversions', () => { | |||
it.each` | |||
value | americanName | |||