Browse Source

Add tests for stupidly big numbers

Add bailout when exponent becomes very huge.
master
TheoryOfNekomata 3 years ago
parent
commit
b24d26de25
15 changed files with 186 additions and 34 deletions
  1. +69
    -22
      src/Converter.ts
  2. +1
    -1
      src/systems/de/base/kilo/combiningPrefix.ts
  3. +2
    -1
      src/systems/en/base/kilo/combiningPrefix.ts
  4. +1
    -1
      src/systems/tl/base/kilo/combiningPrefix.ts
  5. +103
    -9
      tests/Converter.test.ts
  6. +1
    -0
      tests/input/example10.txt
  7. +1
    -0
      tests/input/example11.txt
  8. +1
    -0
      tests/input/example12.txt
  9. +1
    -0
      tests/input/example13.txt
  10. +1
    -0
      tests/input/example14.txt
  11. +1
    -0
      tests/input/example15.txt
  12. +1
    -0
      tests/input/example16.txt
  13. +1
    -0
      tests/input/example17.txt
  14. +1
    -0
      tests/input/example8.txt
  15. +1
    -0
      tests/input/example9.txt

+ 69
- 22
src/Converter.ts View File

@@ -5,41 +5,88 @@ import { ReadStreamOptions, ConvertOptions, NumberSystem, Digit } from './common
export default class Converter {
constructor(private readonly system: NumberSystem, private options: ConvertOptions) {}

public convert = (value: string | number | bigint): string => {
private convertString = (value: string): string => {
let converted: string[] = []
const valueStr = value as string
switch (typeof value!) {
case 'string':
// TODO bail out when exponent is too large!
try {
return this.convert(BigInt(new Big(valueStr).toFixed()))
} catch (e) {}
return this.convert(BigInt(valueStr))
case 'number':
return this.convert(BigInt(value as number))
case 'bigint':
let current = value as bigint
let thousandPower = 0n
while (current > 0n) {
const hundreds = Number(((current % 1000n) / 100n) % 10n) as Digit
const tens = Number(((current % 100n) / 10n) % 10n) as Digit
const ones = Number(current % 10n) as Digit
try {
const big = new Big(value)
const { e: exponent, c: coefficients } = big
const isExponentSmall = exponent < 1000
if (isExponentSmall) {
return this.convert(BigInt(big.toFixed()))
}
let currentPlace = exponent % 3
let thousandPower = BigInt(exponent) / 3n
coefficients.reduce(
([place, hundredsRaw = 0, tensRaw = 0, onesRaw = 0], c) => {
let thePlace = (place + 3) % 3
const nextPlace = (place + 3 - 1) % 3

switch (thePlace) {
case 2:
return [nextPlace, c, tensRaw, onesRaw]
case 1:
return [nextPlace, hundredsRaw, c, onesRaw]
default:
case 0:
break
}

let kiloName: string | undefined
let kiloCount: string
const hundreds = hundredsRaw as Digit
const tens = tensRaw as Digit
const ones = c as Digit
if (!(ones === 0 && tens === 0 && hundreds === 0)) {
if (thousandPower > 0n) {
const { scale = 'short' } = this.options
kiloName = this.system.getKiloName[scale!]!(thousandPower)
}
kiloCount = this.system.getKiloCount(hundreds, tens, ones)
converted.unshift(this.system.joinKilo(kiloCount, kiloName))
converted.push(this.system.joinKilo(kiloCount, kiloName))
}
thousandPower = thousandPower - 1n
return [nextPlace, 0, 0, 0]
},
[currentPlace],
)
} catch {}
return converted.join(' ')
}

private convertBigInt = (value: bigint): string => {
let converted: string[] = []
let current = value as bigint
let thousandPower = 0n
while (current > 0n) {
const hundreds = Number(((current % 1000n) / 100n) % 10n) as Digit
const tens = Number(((current % 100n) / 10n) % 10n) as Digit
const ones = Number(current % 10n) as Digit

thousandPower = thousandPower + 1n
current = current / 1000n
let kiloName: string | undefined
let kiloCount: string
if (!(ones === 0 && tens === 0 && hundreds === 0)) {
if (thousandPower > 0n) {
const { scale = 'short' } = this.options
kiloName = this.system.getKiloName[scale!]!(thousandPower)
}
return converted.join(' ')
kiloCount = this.system.getKiloCount(hundreds, tens, ones)
converted.unshift(this.system.joinKilo(kiloCount, kiloName))
}

thousandPower = thousandPower + 1n
current = current / 1000n
}
return converted.join(' ')
}

public convert = (value: string | number | bigint): string => {
switch (typeof value!) {
case 'string':
return this.convertString(value as string)
case 'number':
return this.convertString(value.toString(10))
case 'bigint':
return this.convertBigInt(value as bigint)
default:
break
}


+ 1
- 1
src/systems/de/base/kilo/combiningPrefix.ts View File

@@ -67,7 +67,7 @@ const combiningPrefix: CombiningPrefix = (kiloHundreds, kiloTens, kiloOnes, kilo
if (
(currentKiloHundreds === 0n && kiloTensNumber > 1) ||
(currentKiloHundreds > 0n && kiloTensNumber !== 1) ||
kiloHundreds >= 10
(kiloHundreds >= 10n && kiloTensNumber !== 1)
) {
prefix += 't'
}


+ 2
- 1
src/systems/en/base/kilo/combiningPrefix.ts View File

@@ -67,10 +67,11 @@ const combiningPrefix: CombiningPrefix = (kiloHundreds, kiloTens, kiloOnes, kilo
if (
(currentKiloHundreds === 0n && kiloTensNumber > 1) ||
(currentKiloHundreds > 0n && kiloTensNumber !== 1) ||
kiloHundreds >= 10
(kiloHundreds >= 10n && kiloTensNumber !== 1)
) {
prefix += 't'
}

return prefix + 'i'
}



+ 1
- 1
src/systems/tl/base/kilo/combiningPrefix.ts View File

@@ -67,7 +67,7 @@ const combiningPrefix: CombiningPrefix = (kiloHundreds, kiloTens, kiloOnes, kilo
if (
(currentKiloHundreds === 0n && kiloTensNumber > 1) ||
(currentKiloHundreds > 0n && kiloTensNumber !== 1) ||
kiloHundreds >= 10
(kiloHundreds >= 10n && kiloTensNumber !== 1)
) {
prefix += 't'
}


+ 103
- 9
tests/Converter.test.ts View File

@@ -8,7 +8,7 @@ describe.each`
${'en'} | ${'English'} | ${English} | ${'short'}
${'en'} | ${'English'} | ${English} | ${'long'}
${'en'} | ${'English'} | ${English} | ${'europeanLong'}
`('$name ($scale count)', ({ locale, system, scaleRaw, }) => {
`('$name ($scaleRaw count)', ({ locale, system, scaleRaw, }) => {
const scale = scaleRaw as Scale
let converter: Converter

@@ -17,18 +17,62 @@ describe.each`
})

describe('on values', () => {
const expected = [
{
value: 1000000,
conversions: {
short: 'one million',
long: 'one million',
europeanLong: 'one million',
},
},
{
value: 123456789,
conversions: {
short: 'one hundred twenty three million four hundred fifty six thousand seven hundred eighty nine',
long: 'one hundred twenty three million four hundred fifty six thousand seven hundred eighty nine',
europeanLong: 'one hundred twenty three million four hundred fifty six thousand seven hundred eighty nine',
},
},
{
value: '1e+6',
conversions: {
short: 'one million',
long: 'one million',
europeanLong: 'one million',
},
},
{
value: '123e+3000003',
conversions: {
short: 'one hundred twenty three milliamilliatillion',
long: 'one hundred twenty three thousand quingenmilliatillion',
europeanLong: 'one hundred twenty three quingenmilliatilliard',
},
},
{
value: '123e+3003003',
conversions: {
short: 'one hundred twenty three milliamilliaunmilliatillion',
long: 'one hundred twenty three thousand quingenmilliaquingentillion',
europeanLong: 'one hundred twenty three quingenmilliaquingentilliard',
},
},
]
test.each`
value | name
${1000000} | ${'one million'}
${123456789} | ${'one hundred twenty three million four hundred fifty six thousand seven hundred eighty nine'}
${'1e+6'} | ${'one million'}
`('should parse $value as $name', ({ value, name, }) => {
expect(converter.convert(value)).toBe(name)
value | conversions
${expected[0].value} | ${expected[0].conversions}
${expected[1].value} | ${expected[1].conversions}
${expected[2].value} | ${expected[2].conversions}
${expected[3].value} | ${expected[3].conversions}
${expected[4].value} | ${expected[4].conversions}
`('should parse $value', ({ value, conversions, }) => {
expect(converter.convert(value)).toBe(conversions[scale])
})
})

describe('on streams', () => {
const ENCODING = 'utf-8'
const ENCODING = 'utf8'

const expected: Record<string, Record<Scale, string>> = {
'million': {
@@ -71,6 +115,56 @@ describe.each`
long: 'one hundred twenty three thousand milliasextrigintillion four hundred fifty six milliasextrigintillion seven hundred eighty nine thousand milliaquintrigintillion two hundred forty six milliaquintrigintillion eight hundred one thousand milliaquattuortrigintillion three hundred fifty seven milliaquattuortrigintillion',
europeanLong: 'one hundred twenty three milliasextrigintilliard four hundred fifty six milliasextrigintillion seven hundred eighty nine milliaquintrigintilliard two hundred forty six milliaquintrigintillion eight hundred one milliaquattuortrigintilliard three hundred fifty seven milliaquattuortrigintillion',
},
'example8': {
short: 'one hundred twenty three nongenseptenoctoginmilliaduoseptuagintillion four hundred fifty six nongenseptenoctoginmilliaunseptuagintillion seven hundred eighty nine nongenseptenoctoginmilliaseptuagintillion two hundred forty six nongenseptenoctoginmillianovemsexagintillion eight hundred one nongenseptenoctoginmilliaoctosexagintillion three hundred fifty seven nongenseptenoctoginmilliaseptensexagintillion',
long: 'one hundred twenty three thousand quadringentrenonaginmilliaquingensextrigintillion four hundred fifty six quadringentrenonaginmilliaquingensextrigintillion seven hundred eighty nine thousand quadringentrenonaginmilliaquingenquintrigintillion two hundred forty six quadringentrenonaginmilliaquingenquintrigintillion eight hundred one thousand quadringentrenonaginmilliaquingenquattuortrigintillion three hundred fifty seven quadringentrenonaginmilliaquingenquattuortrigintillion',
europeanLong: 'one hundred twenty three quadringentrenonaginmilliaquingensextrigintilliard four hundred fifty six quadringentrenonaginmilliaquingensextrigintillion seven hundred eighty nine quadringentrenonaginmilliaquingenquintrigintilliard two hundred forty six quadringentrenonaginmilliaquingenquintrigintillion eight hundred one quadringentrenonaginmilliaquingenquattuortrigintilliard three hundred fifty seven quadringentrenonaginmilliaquingenquattuortrigintillion',
},
'example9': {
short: 'one hundred twenty three nongenseptenoctoginmilliamilliamilliaduoseptuagintillion four hundred fifty six nongenseptenoctoginmilliamilliamilliaunseptuagintillion seven hundred eighty nine nongenseptenoctoginmilliamilliamilliaseptuagintillion two hundred forty six nongenseptenoctoginmilliamilliamillianovemsexagintillion eight hundred one nongenseptenoctoginmilliamilliamilliaoctosexagintillion three hundred fifty seven nongenseptenoctoginmilliamilliamilliaseptensexagintillion',
long: 'one hundred twenty three thousand quadringentrenonaginmilliamilliamilliaquingenmilliamilliasextrigintillion four hundred fifty six quadringentrenonaginmilliamilliamilliaquingenmilliamilliasextrigintillion seven hundred eighty nine thousand quadringentrenonaginmilliamilliamilliaquingenmilliamilliaquintrigintillion two hundred forty six quadringentrenonaginmilliamilliamilliaquingenmilliamilliaquintrigintillion eight hundred one thousand quadringentrenonaginmilliamilliamilliaquingenmilliamilliaquattuortrigintillion three hundred fifty seven quadringentrenonaginmilliamilliamilliaquingenmilliamilliaquattuortrigintillion',
europeanLong: 'one hundred twenty three quadringentrenonaginmilliamilliamilliaquingenmilliamilliasextrigintilliard four hundred fifty six quadringentrenonaginmilliamilliamilliaquingenmilliamilliasextrigintillion seven hundred eighty nine quadringentrenonaginmilliamilliamilliaquingenmilliamilliaquintrigintilliard two hundred forty six quadringentrenonaginmilliamilliamilliaquingenmilliamilliaquintrigintillion eight hundred one quadringentrenonaginmilliamilliamilliaquingenmilliamilliaquattuortrigintilliard three hundred fifty seven quadringentrenonaginmilliamilliamilliaquingenmilliamilliaquattuortrigintillion',
},
'example10': {
short: 'one milliatillion',
long: 'one thousand quingentillion',
europeanLong: 'one quingentilliard',
},
'example11': {
short: 'one duotriginmilliasescenquattuorseptuagintillion',
long: 'one thousand sexdecmilliatrecenseptentrigintillion',
europeanLong: 'one sexdecmilliatrecenseptentrigintilliard',
},
'example12': {
short: 'one milliamilliatillion',
long: 'one thousand quingenmilliatillion',
europeanLong: 'one quingenmilliatilliard',
},
'example13': {
short: 'one octingensexquinquaginmilliamilliaseptingensexseptuaginmilliaquingenundecillion',
long: 'one quadringenoctoviginmilliamilliatrecenoctooctoginmilliaduocensexquinquagintillion',
europeanLong: 'one quadringenoctoviginmilliamilliatrecenoctooctoginmilliaduocensexquinquagintillion',
},
'example14': {
short: 'one milliamilliamilliatillion',
long: 'one thousand quingenmilliamilliatillion',
europeanLong: 'one quingenmilliamilliatilliard',
},
'example15': {
short: 'one trecenunnonaginmilliamilliamilliaquingenunoctoginmilliamilliaduocensexdecmilliacenduononagintillion',
long: 'one thousand cenquinnonaginmilliamilliamilliaseptingennonaginmilliamilliasescenoctomilliasexnonagintillion',
europeanLong: 'one cenquinnonaginmilliamilliamilliaseptingennonaginmilliamilliasescenoctomilliasexnonagintilliard',
},
'example16': {
short: 'one milliamilliamilliamilliatillion',
long: 'one thousand quingenmilliamilliamilliatillion',
europeanLong: 'one quingenmilliamilliamilliatilliard',
},
'example17': {
short: 'one duocenduotriginmilliamilliamilliamilliaduononaginmilliamilliamilliacenseptuaginmilliamilliacennovemdecmillianongensextrigintillion',
long: 'one thousand censexdecmilliamilliamilliamilliasexquadraginmilliamilliamilliaquinoctoginmilliamillianovemquinquaginmillianongenoctosexagintillion',
europeanLong: 'one censexdecmilliamilliamilliamilliasexquadraginmilliamilliamilliaquinoctoginmilliamillianovemquinquaginmillianongenoctosexagintilliard',
},
}

beforeAll(() => {
@@ -78,7 +172,7 @@ describe.each`
try { fs.mkdirSync(`./tests/output/${locale}`) } catch {}
})

test.each(Object.keys(expected))('should correctly parse %s.txt', async (filename) => {
test.each(Object.keys(expected))(`should correctly parse %s.txt`, async (filename) => {
const inputPath = `./tests/input/${filename}.txt`
const outputPath = `./tests/output/${locale}/${filename}.${scale}.txt`



+ 1
- 0
tests/input/example10.txt View File

@@ -0,0 +1 @@
1e+3003

+ 1
- 0
tests/input/example11.txt View File

@@ -0,0 +1 @@
1e+98025

+ 1
- 0
tests/input/example12.txt View File

@@ -0,0 +1 @@
1e+3000003

+ 1
- 0
tests/input/example13.txt View File

@@ -0,0 +1 @@
1e+2570329536

+ 1
- 0
tests/input/example14.txt View File

@@ -0,0 +1 @@
1e+3000000003

+ 1
- 0
tests/input/example15.txt View File

@@ -0,0 +1 @@
1e+1174743648579

+ 1
- 0
tests/input/example16.txt View File

@@ -0,0 +1 @@
1e+3000000000003

+ 1
- 0
tests/input/example17.txt View File

@@ -0,0 +1 @@
1e+696276510359811

+ 1
- 0
tests/input/example8.txt View File

@@ -0,0 +1 @@
1.23456789246801357e+2961221

+ 1
- 0
tests/input/example9.txt View File

@@ -0,0 +1 @@
123.456789246801357e+2961000000219