Browse Source

Initial commit

Add files from pridepack.
master
TheoryOfNekomata 3 years ago
commit
33201e4020
21 changed files with 6633 additions and 0 deletions
  1. +1
    -0
      .gitignore
  2. +9
    -0
      packages/core/.eslintrc
  3. +107
    -0
      packages/core/.gitignore
  4. +47
    -0
      packages/core/package.json
  5. +18
    -0
      packages/core/src/index.ts
  6. +48
    -0
      packages/core/src/locales/de-DE/chongo.test.ts
  7. +204
    -0
      packages/core/src/locales/de-DE/groups.test.ts
  8. +115
    -0
      packages/core/src/locales/de-DE/index.ts
  9. +62
    -0
      packages/core/src/locales/de-DE/plurals.test.ts
  10. +48
    -0
      packages/core/src/locales/en-GB/chongo.test.ts
  11. +104
    -0
      packages/core/src/locales/en-GB/index.ts
  12. +127
    -0
      packages/core/src/locales/en-PH/chongo.test.ts
  13. +26
    -0
      packages/core/src/locales/en-PH/custom.test.ts
  14. +189
    -0
      packages/core/src/locales/en-PH/groups.test.ts
  15. +99
    -0
      packages/core/src/locales/en-PH/index.ts
  16. +39
    -0
      packages/core/src/locales/en-PH/technical.test.ts
  17. +107
    -0
      packages/core/src/utils/common/latinPowers.ts
  18. +70
    -0
      packages/core/src/utils/numeric.ts
  19. +21
    -0
      packages/core/tsconfig.eslint.json
  20. +21
    -0
      packages/core/tsconfig.json
  21. +5171
    -0
      packages/core/yarn.lock

+ 1
- 0
.gitignore View File

@@ -0,0 +1 @@
.idea/

+ 9
- 0
packages/core/.eslintrc View File

@@ -0,0 +1,9 @@
{
"root": true,
"extends": [
"lxsmnsyc/typescript"
],
"parserOptions": {
"project": "./tsconfig.eslint.json"
}
}

+ 107
- 0
packages/core/.gitignore View File

@@ -0,0 +1,107 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.production
.env.development

# parcel-bundler cache (https://parceljs.org/)
.cache

# Next.js build output
.next

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

.npmrc

+ 47
- 0
packages/core/package.json View File

@@ -0,0 +1,47 @@
{
"version": "0.0.0",
"types": "dist/types/index.d.ts",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"exports": {
"require": "./dist/cjs/index.js",
"import": "./dist/esm/index.js"
},
"files": [
"dist"
],
"engines": {
"node": ">=10"
},
"license": "MIT",
"keywords": [
"pridepack",
"number"
"name"
],
"name": "@theoryofnekomata/numerica",
"description": "Gets the name of a number, even if it's stupidly big.",
"devDependencies": {
"@types/bignumber.js": "^5.0.0",
"@types/jest": "^26.0.24",
"@types/node": "^16.3.3",
"eslint": "^7.31.0",
"eslint-config-lxsmnsyc": "^0.2.3",
"pridepack": "^0.10.0",
"tslib": "^2.3.0",
"typescript": "^4.3.5"
},
"peerDependencies": {},
"scripts": {
"prepublish": "pridepack clean && pridepack build",
"build": "pridepack build",
"type-check": "pridepack check",
"lint": "pridepack lint",
"test": "pridepack test --passWithNoTests",
"clean": "pridepack clean",
"watch": "pridepack watch"
},
"dependencies": {
"bignumber.js": "^9.0.1"
}
}

+ 18
- 0
packages/core/src/index.ts View File

@@ -0,0 +1,18 @@
import enPH from './locales/en-PH'
import {
Numeric,
} from './utils/numeric';

type Options = {
groupSeparator: string,
locale?: (xRaw: Numeric, options?: Partial<Omit<Options, 'locale'>>) => string,
}

type GetNumberName = (number: Numeric, options?: Partial<Options>) => string

const getNumberName: GetNumberName = (number, options = {} as Partial<Options>): string => {
const { locale = enPH, ...etcOptions } = options
return locale(number, etcOptions)
}

export default getNumberName

+ 48
- 0
packages/core/src/locales/de-DE/chongo.test.ts View File

@@ -0,0 +1,48 @@
import getNumberName from '../..';
import getLocalizedNumberName from '.';

describe('Landon\'s original test cases', () => {
describe('Basic conversions', () => {
it.each`
value | traditionalEuropeanName
${1} | ${'ein'}
${1000} | ${'eintausend'}
${1000000} | ${'eine Million'}
${1000000000} | ${'eine Milliarde'}
${1000000000000} | ${'eine Billion'}
${1000000000000000} | ${'eine Billiarde'}
${1000000000000000000} | ${'eine Trillion'}
`('converts $value to $traditionalEuropeanName', ({ value, traditionalEuropeanName }) => {
expect(getNumberName(value, { locale: getLocalizedNumberName })).toBe(traditionalEuropeanName)
})
})

describe('Medium size numbers (<= 1e+63)', () => {
describe('Table 1', () => {
it.each`
value | traditionalEuropeanName
${'1e+9'} | ${'Milliarde'}
${'1e+12'} | ${'Billion'}
${'1e+15'} | ${'Billiarde'}
${'1e+18'} | ${'Trillion'}
${'1e+21'} | ${'Trilliarde'}
${'1e+24'} | ${'Quadrillion'}
${'1e+27'} | ${'Quadrilliarde'}
${'1e+30'} | ${'Quintillion'}
${'1e+33'} | ${'Quintilliarde'}
${'1e+36'} | ${'Sextillion'}
${'1e+39'} | ${'Sextilliarde'}
${'1e+42'} | ${'Septillion'}
${'1e+45'} | ${'Septilliarde'}
${'1e+48'} | ${'Octillion'}
${'1e+51'} | ${'Octilliarde'}
${'1e+54'} | ${'Nonillion'}
${'1e+57'} | ${'Nonilliarde'}
${'1e+60'} | ${'Decillion'}
${'1e+63'} | ${'Decilliarde'}
`('converts $value to $traditionalEuropeanName', ({ value, traditionalEuropeanName, }) => {
expect(getNumberName(value, { locale: getLocalizedNumberName })).toBe(`eine ${traditionalEuropeanName}`)
})
})
})
})

+ 204
- 0
packages/core/src/locales/de-DE/groups.test.ts View File

@@ -0,0 +1,204 @@
import getNumberName from '../..';
import getLocalizedNumberName from '.';

describe('Number group conversion', () => {
describe('0 in hundreds place', () => {
describe('0 in tens place', () => {
describe.each`
ones | onesName
${0} | ${'zero'}
${1} | ${'ein'}
${2} | ${'zwei'}
${3} | ${'drei'}
${4} | ${'vier'}
${5} | ${'fünf'}
${6} | ${'sechs'}
${7} | ${'sieben'}
${8} | ${'acht'}
${9} | ${'neun'}
`('$ones in ones place', ({
ones,
onesName,
}) => {
it(`converts ${ones} to ${onesName}`, () => {
expect(getNumberName(ones, { locale: getLocalizedNumberName })).toBe(onesName)
})
})
})

describe('1 in tens place', () => {
describe.each`
ones | onesName
${0} | ${'zehn'}
${1} | ${'elf'}
${2} | ${'zwölf'}
${3} | ${'dreizehn'}
${4} | ${'vierzehn'}
${5} | ${'fünfzehn'}
${6} | ${'sechzehn'}
${7} | ${'siebzehn'}
${8} | ${'achtzehn'}
${9} | ${'neunzehn'}
`('$ones in ones place', ({
ones,
onesName,
}) => {
it(`converts 1${ones} to ${onesName}`, () => {
expect(getNumberName(10 + ones, { locale: getLocalizedNumberName })).toBe(onesName)
})
})
})

describe.each`
tens | tensName
${2} | ${'zwanzig'}
${3} | ${'dreißig'}
${4} | ${'vierzig'}
${5} | ${'fünfzig'}
${6} | ${'sechzig'}
${7} | ${'siebzig'}
${8} | ${'achtzig'}
${9} | ${'neunzig'}
`('$tens in tens place', ({ tens, tensName }) => {
describe('0 in ones place', () => {
const value = tens * 10
const name = tensName
it(`converts ${value} to ${name}`, () => {
expect(getNumberName(value, { locale: getLocalizedNumberName })).toBe(name)
})
})

describe.each`
ones | onesName
${1} | ${'ein'}
${2} | ${'zwei'}
${3} | ${'drei'}
${4} | ${'vier'}
${5} | ${'fünf'}
${6} | ${'sechs'}
${7} | ${'sieben'}
${8} | ${'acht'}
${9} | ${'neun'}
`('$ones in ones place', ({
ones,
onesName,
}) => {
const value = (tens * 10) + ones
const name = [onesName, tensName].join('und').trim()

it(`converts ${value} to ${name}`, () => {
expect(getNumberName(value, { locale: getLocalizedNumberName })).toBe(name)
})
})
})
})

describe.each`
hundreds | hundredsName
${1} | ${'einhundert'}
${2} | ${'zweihundert'}
${3} | ${'dreihundert'}
${4} | ${'vierhundert'}
${5} | ${'fünfhundert'}
${6} | ${'sechshundert'}
${7} | ${'siebenhundert'}
${8} | ${'achthundert'}
${9} | ${'neunhundert'}
`('$hundreds in hundreds place', ({
hundreds,
hundredsName,
}) => {
describe('0 in tens place', () => {
describe.each`
ones | onesName
${0} | ${''}
${1} | ${'ein'}
${2} | ${'zwei'}
${3} | ${'drei'}
${4} | ${'vier'}
${5} | ${'fünf'}
${6} | ${'sechs'}
${7} | ${'sieben'}
${8} | ${'acht'}
${9} | ${'neun'}
`('$ones in ones place', ({
ones,
onesName,
}) => {
const value = (hundreds * 100) + ones
const name = [hundredsName, onesName].join('').trim()
it(`converts ${value} to ${name}`, () => {
expect(getNumberName(value, { locale: getLocalizedNumberName })).toBe(name)
})
})
})

describe('1 in tens place', () => {
describe.each`
ones | onesName
${0} | ${'zehn'}
${1} | ${'elf'}
${2} | ${'zwölf'}
${3} | ${'dreizehn'}
${4} | ${'vierzehn'}
${5} | ${'fünfzehn'}
${6} | ${'sechzehn'}
${7} | ${'siebzehn'}
${8} | ${'achtzehn'}
${9} | ${'neunzehn'}
`('$ones in ones place', ({
ones,
onesName,
}) => {
const value = (hundreds * 100) + 10 + ones
const name = [hundredsName, onesName].join('').trim()
it(`converts ${value} to ${name}`, () => {
expect(getNumberName(value, { locale: getLocalizedNumberName })).toBe(name)
})
})
})

describe.each`
tens | tensName
${2} | ${'zwanzig'}
${3} | ${'dreißig'}
${4} | ${'vierzig'}
${5} | ${'fünfzig'}
${6} | ${'sechzig'}
${7} | ${'siebzig'}
${8} | ${'achtzig'}
${9} | ${'neunzig'}
`('$tens in tens place', ({ tens, tensName }) => {
describe('0 in ones place', () => {
const value = (hundreds * 100) + (tens * 10)
const name = [hundredsName, tensName].join('').trim()
it(`converts ${value} to ${name}`, () => {
expect(getNumberName(value, { locale: getLocalizedNumberName })).toBe(name)
})
})

describe.each`
ones | onesName
${1} | ${'ein'}
${2} | ${'zwei'}
${3} | ${'drei'}
${4} | ${'vier'}
${5} | ${'fünf'}
${6} | ${'sechs'}
${7} | ${'sieben'}
${8} | ${'acht'}
${9} | ${'neun'}
`('$ones in ones place', ({
ones,
onesName,
}) => {
const value = (hundreds * 100) + (tens * 10) + ones
const name = [hundredsName, [onesName, tensName].join('und')].join('').trim()

it(`converts ${value} to ${name}`, () => {
expect(getNumberName(value, { locale: getLocalizedNumberName })).toBe(name)
})
})
})
})
})

+ 115
- 0
packages/core/src/locales/de-DE/index.ts View File

@@ -0,0 +1,115 @@
import BigNumber from 'bignumber.js';
import {
BLANK_DIGIT,
createBlankDigits,
deconstructNumeric,
groupDigits,
NEGATIVE_SIGN,
normalizeNumeric,
Numeric,
} from '../../utils/numeric';
import getLatinPowerName from '../../utils/common/latinPowers';

const config = {
"onesNames": ['zero', 'ein', 'zwei', 'drei', 'vier', 'fünf', 'sechs', 'sieben', 'acht', 'neun'],
"teensNames": ['zehn', 'elf', 'zwölf', 'dreizehn', 'vierzehn', 'fünfzehn', 'sechzehn', 'siebzehn', 'achtzehn', 'neunzehn'],
"tensNames": ['zero', 'zehn', 'zwanzig', 'dreißig', 'vierzig', 'fünfzig', 'sechzig', 'siebzig', 'achtzig', 'neunzig'],
"hundredName": "hundert",
"thousandName": "tausend",
"millia": "millia",
"illion": "illion",
"illiard": "illiarde",
"and": "und",
"hundredsLatinNames": ["", "cen", "duocen", "trecen", "quadringen", "quingen", "sescen", "septingen", "octingen", "nongen"],
"onesLatinNames": ["", "un", "duo", "tre", "quattuor", "quin", "sex", "septen", "octo", "novem"],
"tensLatinNames": ["", "dec", "vigin", "trigin", "quadragin", "quinquagin", "sexagin", "septuagin", "octogin", "nonagin"],
"onesSpecialLatinNames": ["", "m", "b", "tr", "quadr", "quin", "sex", "sep", "oct", "non"],
"negative": "negative",
"grouping": 3,
"latinGrouping": 3
}

const getGroupIndexName = (index: BigNumber, digits: string) => {
if (index.eq(1)) {
return config.thousandName
}

const basicIndex = index.dividedToIntegerBy(2)
const isOdd = index.mod(2).eq(1)
const latinPowerName = getLatinPowerName(basicIndex, isOdd, config)
const latinPowerNameWithCase = latinPowerName.slice(0, 1).toUpperCase() + latinPowerName.slice(1)
if (digits.padStart(config.grouping, BLANK_DIGIT) === '001') {
return latinPowerNameWithCase
}
if (latinPowerNameWithCase.endsWith('e')) {
return latinPowerNameWithCase + 'n'
}
return latinPowerNameWithCase + 'en'
}

const getGroupDigitsName = (digitsRaw: string, index: BigNumber) => {
const { grouping, onesNames, teensNames, tensNames, hundredName } = config
const digits = digitsRaw.padStart(grouping, BLANK_DIGIT)
const [hundreds, tens, ones] = digits.split('').map(s => Number(s))
const names = []
if (hundreds !== 0) {
names.push(onesNames[hundreds])
names.push(hundredName)
}
if (tens === 1) {
names.push(teensNames[ones])
} else if (tens > 1) {
if (ones > 0) {
names.push(onesNames[ones])
names.push(config.and)
}
names.push(tensNames[tens])
} else {
if (hundreds === 0 && ones === 1 && index.gte(2)) {
names.push(onesNames[ones] + 'e')
} else if (hundreds !== 0 && ones > 0 || hundreds === 0) {
names.push(onesNames[ones])
}
}
return names.join('')
}

const getGroupName = (g: [string, BigNumber]) => {
const [digits, index] = g
if (index.lt(1)) {
return getGroupDigitsName(digits, index)
}
if (index.lt(2)) {
return [getGroupDigitsName(digits, index), getGroupIndexName(index, digits)].join('')
}
return [getGroupDigitsName(digits, index), getGroupIndexName(index, digits)].join(' ')
}

type Options = {
groupSeparator: string
}

const getLocalizedNumberName = (xRaw: Numeric, options = {} as Partial<Options>) => {
const {
groupSeparator = ' '
} = options

const x = normalizeNumeric(xRaw)
const { significandDigits, exponent } = deconstructNumeric(x)
const blankDigits = createBlankDigits(config.grouping)
const groups = groupDigits(significandDigits, exponent, config.grouping)

if (groups.length === 1) {
return getGroupName(groups[0])
}

const base = groups.filter(([digits]) => digits !== blankDigits).map(g => getGroupName(g)).join(groupSeparator)

if (x.startsWith(NEGATIVE_SIGN)) {
return [config.negative, base].join(' ')
}

return base
}

export default getLocalizedNumberName

+ 62
- 0
packages/core/src/locales/de-DE/plurals.test.ts View File

@@ -0,0 +1,62 @@
import getNumberName from '../..';
import getLocalizedNumberName from '.';

describe('Plurals', () => {
describe('1 in millions place', () => {
const value = 1000000
const name = 'eine Million'
it(`converts ${value} to ${name}`, () => {
expect(getNumberName(value, { locale: getLocalizedNumberName })).toBe(name)
})
})

describe.each`
ones | onesName
${2} | ${'zwei'}
${3} | ${'drei'}
${4} | ${'vier'}
${5} | ${'fünf'}
${6} | ${'sechs'}
${7} | ${'sieben'}
${8} | ${'acht'}
${9} | ${'neun'}
`('$ones in millions place', ({
ones,
onesName,
}) => {
const value = ones * 1000000
const name = `${onesName} Millionen`
it(`converts ${value} to ${name}`, () => {
expect(getNumberName(value, { locale: getLocalizedNumberName })).toBe(name)
})
})

describe('1 in billions place', () => {
const value = 1000000000
const name = 'eine Milliarde'
it(`converts ${value} to ${name}`, () => {
expect(getNumberName(value, { locale: getLocalizedNumberName })).toBe(name)
})
})

describe.each`
ones | onesName
${2} | ${'zwei'}
${3} | ${'drei'}
${4} | ${'vier'}
${5} | ${'fünf'}
${6} | ${'sechs'}
${7} | ${'sieben'}
${8} | ${'acht'}
${9} | ${'neun'}
`('$ones in billions place', ({
ones,
onesName,
}) => {
const value = ones * 1000000000
const name = `${onesName} Milliarden`
it(`converts ${value} to ${name}`, () => {
expect(getNumberName(value, { locale: getLocalizedNumberName })).toBe(name)
})
})
})

+ 48
- 0
packages/core/src/locales/en-GB/chongo.test.ts View File

@@ -0,0 +1,48 @@
import getNumberName from '../..';
import getLocalizedNumberName from '.';

describe('Landon\'s original test cases', () => {
describe('Basic conversions', () => {
it.each`
value | traditionalBritishName
${1} | ${'one'}
${1000} | ${'one thousand'}
${1000000} | ${'one million'}
${1000000000} | ${'one thousand million'}
${1000000000000} | ${'one billion'}
${1000000000000000} | ${'one thousand billion'}
${1000000000000000000} | ${'one trillion'}
`('converts $value to $traditionalBritishName', ({ value, traditionalBritishName }) => {
expect(getNumberName(value, { locale: getLocalizedNumberName })).toBe(traditionalBritishName)
})
})

describe('Medium size numbers (<= 1e+63)', () => {
describe('Table 1', () => {
it.each`
value | traditionalBritishName
${'1e+9'} | ${'thousand million'}
${'1e+12'} | ${'billion'}
${'1e+15'} | ${'thousand billion'}
${'1e+18'} | ${'trillion'}
${'1e+21'} | ${'thousand trillion'}
${'1e+24'} | ${'quadrillion'}
${'1e+27'} | ${'thousand quadrillion'}
${'1e+30'} | ${'quintillion'}
${'1e+33'} | ${'thousand quintillion'}
${'1e+36'} | ${'sextillion'}
${'1e+39'} | ${'thousand sextillion'}
${'1e+42'} | ${'septillion'}
${'1e+45'} | ${'thousand septillion'}
${'1e+48'} | ${'octillion'}
${'1e+51'} | ${'thousand octillion'}
${'1e+54'} | ${'nonillion'}
${'1e+57'} | ${'thousand nonillion'}
${'1e+60'} | ${'decillion'}
${'1e+63'} | ${'thousand decillion'}
`('converts $value to $traditionalBritishName', ({ value, traditionalBritishName, }) => {
expect(getNumberName(value, { locale: getLocalizedNumberName })).toBe(`one ${traditionalBritishName}`)
})
})
})
})

+ 104
- 0
packages/core/src/locales/en-GB/index.ts View File

@@ -0,0 +1,104 @@
import BigNumber from 'bignumber.js';
import {
BLANK_DIGIT,
createBlankDigits,
deconstructNumeric,
groupDigits,
NEGATIVE_SIGN,
normalizeNumeric,
Numeric,
} from '../../utils/numeric';
import getLatinPowerName from '../../utils/common/latinPowers';

const config = {
"onesNames": ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"],
"teensNames": ["ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"],
"tensNames": ["zero", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"],
"hundredName": "hundred",
"thousandName": "thousand",
"millia": "millia",
"illion": "illion",
"hundredsLatinNames": ["", "cen", "duocen", "trecen", "quadringen", "quingen", "sescen", "septingen", "octingen", "nongen"],
"onesLatinNames": ["", "un", "duo", "tre", "quattuor", "quin", "sex", "septen", "octo", "novem"],
"tensLatinNames": ["", "dec", "vigin", "trigin", "quadragin", "quinquagin", "sexagin", "septuagin", "octogin", "nonagin"],
"onesSpecialLatinNames": ["", "m", "b", "tr", "quadr", "quin", "sex", "sep", "oct", "non"],
"negative": "negative",
"grouping": 3,
"latinGrouping": 3
}

const getGroupIndexName = (index: BigNumber) => {
if (index.eq(1)) {
return config.thousandName
}

const basicIndex = index.dividedToIntegerBy(2)
const isOdd = false
const latinPowerName = getLatinPowerName(basicIndex, isOdd, config)
if (index.mod(2).eq(1)) {
return [config.thousandName, latinPowerName].join(' ')
}

return latinPowerName
}

const getGroupDigitsName = (digitsRaw: string) => {
const { grouping, onesNames, teensNames, tensNames, hundredName } = config
const digits = digitsRaw.padStart(grouping, BLANK_DIGIT)
const [hundreds, tens, ones] = digits.split('').map(s => Number(s))
const names = []
if (hundreds !== 0) {
names.push(onesNames[hundreds])
names.push(hundredName)
}
if (tens === 1) {
names.push(teensNames[ones])
} else if (tens > 1) {
names.push(tensNames[tens])
if (ones > 0) {
names.push(onesNames[ones])
}
} else {
if (hundreds !== 0 && ones > 0 || hundreds === 0) {
names.push(onesNames[ones])
}
}
return names.join(' ')
}

const getGroupName = (g: [string, BigNumber]) => {
const [digits, index] = g
if (index.lt(1)) {
return getGroupDigitsName(digits)
}
return [getGroupDigitsName(digits), getGroupIndexName(index)].join(' ')
}

type Options = {
groupSeparator: string
}

const getLocalizedNumberName = (xRaw: Numeric, options = {} as Partial<Options>) => {
const {
groupSeparator = ' '
} = options

const x = normalizeNumeric(xRaw)
const { significandDigits, exponent } = deconstructNumeric(x)
const blankDigits = createBlankDigits(config.grouping)
const groups = groupDigits(significandDigits, exponent, config.grouping)

if (groups.length === 1) {
return getGroupName(groups[0])
}

const base = groups.filter(([digits]) => digits !== blankDigits).map(g => getGroupName(g)).join(groupSeparator)

if (x.startsWith(NEGATIVE_SIGN)) {
return [config.negative, base].join(' ')
}

return base
}

export default getLocalizedNumberName

+ 127
- 0
packages/core/src/locales/en-PH/chongo.test.ts View File

@@ -0,0 +1,127 @@
import getNumberName from '../..';

describe('Landon\'s original test cases', () => {
describe('Basic conversions', () => {
it.each`
value | americanName
${1} | ${'one'}
${1000} | ${'one thousand'}
${1000000} | ${'one million'}
${1000000000} | ${'one billion'}
${1000000000000} | ${'one trillion'}
${1000000000000000} | ${'one quadrillion'}
${1000000000000000000} | ${'one quintillion'}
`('converts $value to $americanName', ({ value, americanName }) => {
expect(getNumberName(value)).toBe(americanName)
})

it('converts 987654321 to nine hundred eighty seven million six hundred fifty four thousand three hundred twenty one', () => {
expect(getNumberName(987654321)).toBe('nine hundred eighty seven million six hundred fifty four thousand three hundred twenty one')
})

it('converts 123456789246801357 to one hundred twenty three quadrillion four hundred fifty six trillion seven hundred eighty nine billion two hundred forty six million eight hundred one thousand three hundred fifty seven', () => {
expect(getNumberName('123456789246801357')).toBe('one hundred twenty three quadrillion four hundred fifty six trillion seven hundred eighty nine billion two hundred forty six million eight hundred one thousand three hundred fifty seven')
})
it('converts 123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 to one hundred twenty three duoseptuagintillion four hundred fifty six unseptuagintillion seven hundred eighty nine septuagintillion two hundred forty six novemsexagintillion eight hundred one octosexagintillion three hundred fifty seven septensexagintillion', () => {
expect(getNumberName('123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')).toBe('one hundred twenty three duoseptuagintillion four hundred fifty six unseptuagintillion seven hundred eighty nine septuagintillion two hundred forty six novemsexagintillion eight hundred one octosexagintillion three hundred fifty seven septensexagintillion')
})
it('converts 123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 to one hundred twenty three cenduoseptuagintillion four hundred fifty six cenunseptuagintillion seven hundred eighty nine censeptuagintillion two hundred forty six cennovemsexagintillion eight hundred one cenoctosexagintillion three hundred fifty seven censeptensexagintillion', () => {
expect(getNumberName('123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')).toBe('one hundred twenty three cenduoseptuagintillion four hundred fifty six cenunseptuagintillion seven hundred eighty nine censeptuagintillion two hundred forty six cennovemsexagintillion eight hundred one cenoctosexagintillion three hundred fifty seven censeptensexagintillion')
})

it('converts 123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 to one hundred twenty three trecenduoseptuagintillion four hundred fifty six trecenunseptuagintillion seven hundred eighty nine trecenseptuagintillion two hundred forty six trecennovemsexagintillion eight hundred one trecenoctosexagintillion three hundred fifty seven trecenseptensexagintillion', () => {
expect(getNumberName('123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')).toBe('one hundred twenty three trecenduoseptuagintillion four hundred fifty six trecenunseptuagintillion seven hundred eighty nine trecenseptuagintillion two hundred forty six trecennovemsexagintillion eight hundred one trecenoctosexagintillion three hundred fifty seven trecenseptensexagintillion')
})
})

describe('Medium size numbers (<= 1e+63)', () => {
describe('Table 1', () => {
it.each`
value | americanName
${'1e+9'} | ${'billion'}
${'1e+12'} | ${'trillion'}
${'1e+15'} | ${'quadrillion'}
${'1e+18'} | ${'quintillion'}
${'1e+21'} | ${'sextillion'}
${'1e+24'} | ${'septillion'}
${'1e+27'} | ${'octillion'}
${'1e+30'} | ${'nonillion'}
${'1e+33'} | ${'decillion'}
${'1e+36'} | ${'undecillion'}
${'1e+39'} | ${'duodecillion'}
${'1e+42'} | ${'tredecillion'}
${'1e+45'} | ${'quattuordecillion'}
${'1e+48'} | ${'quindecillion'}
${'1e+51'} | ${'sexdecillion'}
${'1e+54'} | ${'septendecillion'}
${'1e+57'} | ${'octodecillion'}
${'1e+60'} | ${'novemdecillion'}
${'1e+63'} | ${'vigintillion'}
`('converts $value to $americanName', ({ value, americanName }) => {
expect(getNumberName(value)).toBe(`one ${americanName}`)
})
})
})

describe('Large size numbers (< 1e+303)', () => {
it.each`
value | americanName
${'1e+66'} | ${'unvigintillion'}
${'1e+69'} | ${'duovigintillion'}
${'1e+72'} | ${'trevigintillion'}
${'1e+75'} | ${'quattuorvigintillion'}
${'1e+78'} | ${'quinvigintillion'}
${'1e+81'} | ${'sexvigintillion'}
${'1e+84'} | ${'septenvigintillion'}
${'1e+87'} | ${'octovigintillion'}
${'1e+90'} | ${'novemvigintillion'}
${'1e+93'} | ${'trigintillion'}
${'1e+123'} | ${'quadragintillion'}
${'1e+150'} | ${'novemquadragintillion'}
${'1e+153'} | ${'quinquagintillion'}
${'1e+156'} | ${'unquinquagintillion'}
${'1e+183'} | ${'sexagintillion'}
${'1e+213'} | ${'septuagintillion'}
${'1e+222'} | ${'treseptuagintillion'}
${'1e+243'} | ${'octogintillion'}
${'1e+273'} | ${'nonagintillion'}
${'1e+300'} | ${'novemnonagintillion'}
`('converts $value to $americanName', ({ value, americanName }) => {
expect(getNumberName(value)).toBe(`one ${americanName}`)
})
})

describe('Gigantic size numbers (< 1e+3003)', () => {
it.each`
value | americanName
${'1e+303'} | ${'centillion'}
${'1e+306'} | ${'cenuntillion'}
${'1e+309'} | ${'cenduotillion'}
${'1e+312'} | ${'centretillion'}
${'1e+315'} | ${'cenquattuortillion'}
${'1e+318'} | ${'cenquintillion'}
${'1e+321'} | ${'censextillion'}
${'1e+324'} | ${'censeptentillion'}
${'1e+327'} | ${'cenoctotillion'}
${'1e+330'} | ${'cennovemtillion'}
${'1e+603'} | ${'duocentillion'}
${'1e+903'} | ${'trecentillion'}
${'1e+1203'} | ${'quadringentillion'}
${'1e+1503'} | ${'quingentillion'}
${'1e+1803'} | ${'sescentillion'}
${'1e+2103'} | ${'septingentillion'}
${'1e+2403'} | ${'octingentillion'}
${'1e+2703'} | ${'nongentillion'}
`('converts $value to $americanName', ({ value, americanName }) => {
expect(getNumberName(value)).toBe(`one ${americanName}`)
})
})

describe('Titanic size numbers (< 1e+3000003', () => {
it('converts 123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 to one hundred twenty three milliaduoseptuagintillion four hundred fifty six milliaunseptuagintillion seven hundred eighty nine milliaseptuagintillion two hundred forty six millianovemsexagintillion eight hundred one milliaoctosexagintillion three hundred fifty seven milliaseptensexagintillion', () => {
expect(getNumberName('123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')).toBe('one hundred twenty three milliaduoseptuagintillion four hundred fifty six milliaunseptuagintillion seven hundred eighty nine milliaseptuagintillion two hundred forty six millianovemsexagintillion eight hundred one milliaoctosexagintillion three hundred fifty seven milliaseptensexagintillion')
})
})
})

+ 26
- 0
packages/core/src/locales/en-PH/custom.test.ts View File

@@ -0,0 +1,26 @@
import getNumberName from '../..';

describe('Custom numbers', () => {
it('converts 123456000 to one hundred twenty three million four hundred fifty six thousand', () => {
expect(getNumberName(123456000)).toBe('one hundred twenty three million four hundred fifty six thousand')
})
it('converts 123456000 to one hundred twenty three million four hundred fifty six thousand nine', () => {
expect(getNumberName(123456009)).toBe('one hundred twenty three million four hundred fifty six thousand nine')
})

it('converts 123000789 to one hundred twenty three million seven hundred eighty nine', () => {
expect(getNumberName(123000789)).toBe('one hundred twenty three million seven hundred eighty nine')
})

it('converts 123050789 to one hundred twenty three million fifty thousand seven hundred eighty nine', () => {
expect(getNumberName(123050789)).toBe('one hundred twenty three million fifty thousand seven hundred eighty nine')
})

it('converts 123456789 to one hundred twenty three million four hundred fifty six thousand seven hundred eighty nine', () => {
expect(getNumberName(123456789)).toBe('one hundred twenty three million four hundred fifty six thousand seven hundred eighty nine')
})

it('converts -123456789 to negative one hundred twenty three million four hundred fifty six thousand seven hundred eighty nine', () => {
expect(getNumberName(-123456789)).toBe('negative one hundred twenty three million four hundred fifty six thousand seven hundred eighty nine')
})
})

+ 189
- 0
packages/core/src/locales/en-PH/groups.test.ts View File

@@ -0,0 +1,189 @@
import getNumberName from '../..';

describe('Number group conversion', () => {
describe('0 in hundreds place', () => {
describe('0 in tens place', () => {
describe.each`
ones | onesName
${0} | ${'zero'}
${1} | ${'one'}
${2} | ${'two'}
${3} | ${'three'}
${4} | ${'four'}
${5} | ${'five'}
${6} | ${'six'}
${7} | ${'seven'}
${8} | ${'eight'}
${9} | ${'nine'}
`('$ones in ones place', ({
ones,
onesName,
}) => {
it(`converts ${ones} to ${onesName}`, () => {
expect(getNumberName(ones)).toBe(onesName)
})
})
})

describe('1 in tens place', () => {
describe.each`
ones | onesName
${0} | ${'ten'}
${1} | ${'eleven'}
${2} | ${'twelve'}
${3} | ${'thirteen'}
${4} | ${'fourteen'}
${5} | ${'fifteen'}
${6} | ${'sixteen'}
${7} | ${'seventeen'}
${8} | ${'eighteen'}
${9} | ${'nineteen'}
`('$ones in ones place', ({
ones,
onesName,
}) => {
it(`converts 1${ones} to ${onesName}`, () => {
expect(getNumberName(10 + ones)).toBe(onesName)
})
})
})

describe.each`
tens | tensName
${2} | ${'twenty'}
${3} | ${'thirty'}
${4} | ${'forty'}
${5} | ${'fifty'}
${6} | ${'sixty'}
${7} | ${'seventy'}
${8} | ${'eighty'}
${9} | ${'ninety'}
`('$tens in tens place', ({ tens, tensName }) => {
describe.each`
ones | onesName
${0} | ${''}
${1} | ${'one'}
${2} | ${'two'}
${3} | ${'three'}
${4} | ${'four'}
${5} | ${'five'}
${6} | ${'six'}
${7} | ${'seven'}
${8} | ${'eight'}
${9} | ${'nine'}
`('$ones in ones place', ({
ones,
onesName,
}) => {
const value = (tens * 10) + ones
const name = [tensName, onesName].join(' ').trim()

it(`converts ${value} to ${name}`, () => {
expect(getNumberName(value)).toBe(name)
})
})
})
})

describe.each`
hundreds | hundredsName
${1} | ${'one hundred'}
${2} | ${'two hundred'}
${3} | ${'three hundred'}
${4} | ${'four hundred'}
${5} | ${'five hundred'}
${6} | ${'six hundred'}
${7} | ${'seven hundred'}
${8} | ${'eight hundred'}
${9} | ${'nine hundred'}
`('$hundreds in hundreds place', ({
hundreds,
hundredsName,
}) => {
describe('0 in tens place', () => {
describe.each`
ones | onesName
${0} | ${''}
${1} | ${'one'}
${2} | ${'two'}
${3} | ${'three'}
${4} | ${'four'}
${5} | ${'five'}
${6} | ${'six'}
${7} | ${'seven'}
${8} | ${'eight'}
${9} | ${'nine'}
`('$ones in ones place', ({
ones,
onesName,
}) => {
const value = (hundreds * 100) + ones
const name = [hundredsName, onesName].join(' ').trim()
it(`converts ${value} to ${name}`, () => {
expect(getNumberName(value)).toBe(name)
})
})
})

describe('1 in tens place', () => {
describe.each`
ones | onesName
${0} | ${'ten'}
${1} | ${'eleven'}
${2} | ${'twelve'}
${3} | ${'thirteen'}
${4} | ${'fourteen'}
${5} | ${'fifteen'}
${6} | ${'sixteen'}
${7} | ${'seventeen'}
${8} | ${'eighteen'}
${9} | ${'nineteen'}
`('$ones in ones place', ({
ones,
onesName,
}) => {
const value = (hundreds * 100) + 10 + ones
const name = [hundredsName, onesName].join(' ').trim()
it(`converts ${value} to ${name}`, () => {
expect(getNumberName(value)).toBe(name)
})
})
})

describe.each`
tens | tensName
${2} | ${'twenty'}
${3} | ${'thirty'}
${4} | ${'forty'}
${5} | ${'fifty'}
${6} | ${'sixty'}
${7} | ${'seventy'}
${8} | ${'eighty'}
${9} | ${'ninety'}
`('$tens in tens place', ({ tens, tensName }) => {
describe.each`
ones | onesName
${0} | ${''}
${1} | ${'one'}
${2} | ${'two'}
${3} | ${'three'}
${4} | ${'four'}
${5} | ${'five'}
${6} | ${'six'}
${7} | ${'seven'}
${8} | ${'eight'}
${9} | ${'nine'}
`('$ones in ones place', ({
ones,
onesName,
}) => {
const value = (hundreds * 100) + (tens * 10) + ones
const name = [hundredsName, tensName, onesName].join(' ').trim()

it(`converts ${value} to ${name}`, () => {
expect(getNumberName(value)).toBe(name)
})
})
})
})
})

+ 99
- 0
packages/core/src/locales/en-PH/index.ts View File

@@ -0,0 +1,99 @@
import BigNumber from 'bignumber.js';
import {
BLANK_DIGIT,
createBlankDigits,
deconstructNumeric,
groupDigits,
NEGATIVE_SIGN,
normalizeNumeric,
Numeric,
} from '../../utils/numeric';
import getLatinPowerName from '../../utils/common/latinPowers';

const config = {
"onesNames": ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"],
"teensNames": ["ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"],
"tensNames": ["zero", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"],
"hundredName": "hundred",
"thousandName": "thousand",
"millia": "millia",
"illion": "illion",
"hundredsLatinNames": ["", "cen", "duocen", "trecen", "quadringen", "quingen", "sescen", "septingen", "octingen", "nongen"],
"onesLatinNames": ["", "un", "duo", "tre", "quattuor", "quin", "sex", "septen", "octo", "novem"],
"tensLatinNames": ["", "dec", "vigin", "trigin", "quadragin", "quinquagin", "sexagin", "septuagin", "octogin", "nonagin"],
"onesSpecialLatinNames": ["", "m", "b", "tr", "quadr", "quin", "sex", "sep", "oct", "non"],
"negative": "negative",
"grouping": 3,
"latinGrouping": 3
}

const getGroupIndexName = (index: BigNumber) => {
if (index.eq(1)) {
return config.thousandName
}

const basicIndex = index.minus(1)
const isOdd = false
return getLatinPowerName(basicIndex, isOdd, config)
}

const getGroupDigitsName = (digitsRaw: string) => {
const { grouping, onesNames, teensNames, tensNames, hundredName } = config
const digits = digitsRaw.padStart(grouping, BLANK_DIGIT)
const [hundreds, tens, ones] = digits.split('').map(s => Number(s))
const names = []
if (hundreds !== 0) {
names.push(onesNames[hundreds])
names.push(hundredName)
}
if (tens === 1) {
names.push(teensNames[ones])
} else if (tens > 1) {
names.push(tensNames[tens])
if (ones > 0) {
names.push(onesNames[ones])
}
} else {
if (hundreds !== 0 && ones > 0 || hundreds === 0) {
names.push(onesNames[ones])
}
}
return names.join(' ')
}

const getGroupName = (g: [string, BigNumber]) => {
const [digits, index] = g
if (index.lt(1)) {
return getGroupDigitsName(digits)
}
return [getGroupDigitsName(digits), getGroupIndexName(index)].join(' ')
}

type Options = {
groupSeparator: string
}

const getLocalizedNumberName = (xRaw: Numeric, options = {} as Partial<Options>) => {
const {
groupSeparator = ' '
} = options

const x = normalizeNumeric(xRaw)
const { significandDigits, exponent } = deconstructNumeric(x)
const blankDigits = createBlankDigits(config.grouping)
const groups = groupDigits(significandDigits, exponent, config.grouping)

if (groups.length === 1) {
return getGroupName(groups[0])
}

const base = groups.filter(([digits]) => digits !== blankDigits).map(g => getGroupName(g)).join(groupSeparator)

if (x.startsWith(NEGATIVE_SIGN)) {
return [config.negative, base].join(' ')
}

return base
}

export default getLocalizedNumberName

+ 39
- 0
packages/core/src/locales/en-PH/technical.test.ts View File

@@ -0,0 +1,39 @@
import getNumberName from '../..';

describe('Technical numbers', () => {
describe('Number.MAX_SAFE_INTEGER', () => {
it('converts Number.MAX_SAFE_INTEGER to nine quadrillion seven trillion one hundred ninety nine billion two hundred fifty four million seven hundred forty thousand nine hundred ninety one', () => {
expect(Number.MAX_SAFE_INTEGER).toBe(9_007_199_254_740_991)
expect(getNumberName(Number.MAX_SAFE_INTEGER)).toBe('nine quadrillion seven trillion one hundred ninety nine billion two hundred fifty four million seven hundred forty thousand nine hundred ninety one')
})
})

describe('Powers of 2', () => {
it.each`
value | name
${2 ** 0} | ${'one'}
${2 ** 1} | ${'two'}
${2 ** 2} | ${'four'}
${2 ** 3} | ${'eight'}
${2 ** 4} | ${'sixteen'}
${2 ** 5} | ${'thirty two'}
${2 ** 6} | ${'sixty four'}
${2 ** 7} | ${'one hundred twenty eight'}
${2 ** 8} | ${'two hundred fifty six'}
${2 ** 9} | ${'five hundred twelve'}
${2 ** 10} | ${'one thousand twenty four'}
${2 ** 11} | ${'two thousand forty eight'}
${2 ** 12} | ${'four thousand ninety six'}
${2 ** 13} | ${'eight thousand one hundred ninety two'}
${2 ** 14} | ${'sixteen thousand three hundred eighty four'}
${2 ** 15} | ${'thirty two thousand seven hundred sixty eight'}
${2 ** 16} | ${'sixty five thousand five hundred thirty six'}
${2 ** 17} | ${'one hundred thirty one thousand seventy two'}
${2 ** 18} | ${'two hundred sixty two thousand one hundred forty four'}
${2 ** 19} | ${'five hundred twenty four thousand two hundred eighty eight'}
${2 ** 20} | ${'one million forty eight thousand five hundred seventy six'}
`('converts $value to $name', ({ value, name }) => {
expect(getNumberName(value)).toBe(name)
})
})
})

+ 107
- 0
packages/core/src/utils/common/latinPowers.ts View File

@@ -0,0 +1,107 @@
import {BLANK_DIGIT, createBlankDigits, deconstructNumeric, Group, groupDigits, normalizeNumeric} from '../numeric';
import BigNumber from 'bignumber.js';

interface Config {
hundredsLatinNames: string[],
onesLatinNames: string[],
onesSpecialLatinNames: string[],
tensLatinNames: string[],
illion: string,
illiard?: string,
millia: string,
}

const getLatinPowerGroupDigitsName = (latinRaw: string, special: boolean, config: Config) => {
const digits = latinRaw.padStart(3, BLANK_DIGIT)
const [hundreds, tens, ones] = digits.split('').map(s => Number(s))
const names = []

if (hundreds > 0) {
names.push(config.hundredsLatinNames[hundreds])
names.push(config.onesLatinNames[ones])
names.push(config.tensLatinNames[tens])
} else {
if (tens > 0) {
names.push(config.onesLatinNames[ones])
names.push(config.tensLatinNames[tens])
} else {
if (special) {
names.push(config.onesSpecialLatinNames[ones])
} else if (ones > 1) {
names.push(config.onesLatinNames[ones])
}
}
}
return names.join('')
}

const getLatinPowerSuffix = (latinRaw: string, isOdd: boolean, special: boolean, config: Config) => {
const digits = latinRaw.padStart(3, BLANK_DIGIT)
const [hundreds, tens, ones] = digits.split('').map(s => Number(s))
const suffix = isOdd ? config.illiard : config.illion

if (hundreds > 0) {
if (tens !== 1) {
return 't' + suffix
}
return suffix
}
if (tens > 0) {
switch (tens) {
case 1:
return suffix
default:
break
}
return 't' + suffix
}
switch (ones) {
case 1:
case 2:
case 3:
case 4:
return special ? suffix : 't' + suffix
case 5:
case 6:
case 7:
return 't' + suffix
case 8:
case 9:
return suffix
default:
break
}
return ''
}

const getLatinPowerGroupName = (g: Group, config: Config) => {
const [digits, index] = g
if (index.lt(1)) {
return getLatinPowerGroupDigitsName(digits, index.eq(0), config)
}
let milliaSuffix = ''
for (let i = new BigNumber(0); i.lt(index); i = i.plus(1)) {
milliaSuffix += config.millia
}
return [getLatinPowerGroupDigitsName(digits, index.eq(0), config), milliaSuffix].join('')
}

const getLatinPowerName = (latinRaw: BigNumber, isOdd: boolean, config: Config) => {
const x = normalizeNumeric(latinRaw)
const { significandDigits, exponent } = deconstructNumeric(x)
const blankDigits = createBlankDigits(3)
const groups = groupDigits(significandDigits, exponent, 3)

if (groups.length === 1) {
return [getLatinPowerGroupName(groups[0], config), getLatinPowerSuffix(groups[0][0], isOdd, true, config)].join('')
}

const visibleGroups = groups.filter(([digits]) => digits !== blankDigits)
const [lastVisibleGroup] = visibleGroups.slice(-1)
return [
...visibleGroups.map(g => getLatinPowerGroupName(g, config)),
getLatinPowerSuffix(lastVisibleGroup[0], isOdd, lastVisibleGroup[1].eq(0), config),
].join('')
}

export default getLatinPowerName

+ 70
- 0
packages/core/src/utils/numeric.ts View File

@@ -0,0 +1,70 @@
import BigNumber from 'bignumber.js';

const EXPONENT_SEPARATOR = 'e'
const SIGNIFICAND_DECIMAL_POINT = '.'

export const NEGATIVE_SIGN = '-'

export const BLANK_DIGIT = '0' // must be a value where Number(BLANK_DIGIT) === 0

export type Numeric = number | bigint | string | BigNumber

export type Group = [string, BigNumber]

export const normalizeNumeric = (x: Numeric): string => {
try {
switch (typeof x) {
case 'number':
return new BigNumber(x).toString(10)
case 'bigint':
return x.toString(10)
case 'string':
return new BigNumber(x).toString(10)
case 'object':
return x.toString(10)
default:
break
}
} catch {
throw new RangeError('Not a valid numeric value in the current locale.')
}
throw new TypeError('Not a valid numeric value in any locale.')
}

export const deconstructNumeric = (x: string) => {
const absolute = x.replaceAll(NEGATIVE_SIGN, '')
if (!absolute.includes(EXPONENT_SEPARATOR)) {
return {
exponent: new BigNumber(absolute.length - 1),
significandDigits: absolute,
}
}
const [significandStrExp, exponentStr] = absolute.split(EXPONENT_SEPARATOR)
return {
exponent: new BigNumber(exponentStr),
significandDigits: significandStrExp.replaceAll(SIGNIFICAND_DECIMAL_POINT, '')
}
}

export const createBlankDigits = (grouping: number) => new Array<string>(grouping).fill(BLANK_DIGIT).join('')

export const groupDigits = (significandStr: string, exponent: BigNumber, grouping: number) => {
const blankDigits = createBlankDigits(grouping)
return significandStr
.split('')
.reduceRight(
(theGroups, c, i): any => {
const currentGroupIndex = exponent.minus(i).dividedToIntegerBy(grouping).minus(1)
const [lastGroup = [blankDigits, currentGroupIndex.plus(1)] as Group] = theGroups
const [digits, groupIndex] = lastGroup
const currentPlaceValue = exponent.minus(i).mod(grouping)
if (currentPlaceValue.eq(0)) {
return [[`${blankDigits.slice(0, -c.length)}${c}`, currentGroupIndex.plus(1)], ...theGroups]
}
const currentDigitStringIndex = new BigNumber(grouping).minus(1).minus(currentPlaceValue).toNumber()
const newDigits = digits.slice(0, currentDigitStringIndex) + c + digits.slice(currentDigitStringIndex + c.length)
return [[newDigits, groupIndex] as Group, ...theGroups.slice(1)]
},
[] as Group[],
)
}

+ 21
- 0
packages/core/tsconfig.eslint.json View File

@@ -0,0 +1,21 @@
{
"exclude": ["node_modules"],
"include": ["src", "types", "test"],
"compilerOptions": {
"module": "ESNext",
"lib": ["ESNext"],
"importHelpers": true,
"declaration": true,
"sourceMap": true,
"rootDir": "./",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"jsx": "react",
"esModuleInterop": true,
"target": "ES2017"
}
}

+ 21
- 0
packages/core/tsconfig.json View File

@@ -0,0 +1,21 @@
{
"exclude": ["node_modules"],
"include": ["src", "types"],
"compilerOptions": {
"module": "ESNext",
"lib": ["ESNext"],
"importHelpers": true,
"declaration": true,
"sourceMap": true,
"rootDir": "./src",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"jsx": "react",
"esModuleInterop": true,
"target": "ES2017"
}
}

+ 5171
- 0
packages/core/yarn.lock
File diff suppressed because it is too large
View File


Loading…
Cancel
Save