@technobuddha/library
Version:
A large library of useful functions
399 lines (398 loc) • 12.3 kB
JavaScript
import isFinite from 'lodash/isFinite';
import isNaN from 'lodash/isNaN';
import { empty, space } from '../constants';
const ones = ['zero',
'one',
'two',
'three',
'four',
'five',
'six',
'seven',
'eight',
'nine',
'ten',
'eleven',
'twelve',
'thirteen',
'fourteen',
'fifteen',
'sixteen',
'seventeen',
'eighteen',
'nineteen'];
const tens = ['twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'];
const ZERO = 0;
const TEN = 10;
const TWENTY = 20;
const ONE_HUNDRED = 100;
/**
* Convert a number into text (the cardinal number)
*
* @remark There is no limit to the numbers that can be expressed, however Javascript/Typescript can only represent numbers
* up to uncentillions (1e308).
*
* @param input The number
* @param __namedParameters see {@link Options}
* @returns The number spelled out
*
* @default groups Infinity
* @default digits false
* @default and (empty)
* @default hyphen (space)
*/
export function cardinal(input, { groups = Infinity, digits = false, ...options } = {}) {
const words = [];
if (isNaN(input)) {
words.push('not a number');
}
else {
if (input < ZERO) {
words.push('negative');
input = -input;
}
if (isFinite(input)) {
if (input === 0) {
words.push(ones[0]);
}
else {
let { mantissa, exponent } = breakdown(input, groups);
while (Number.parseInt(mantissa, 10) > 0 && exponent >= 0 && groups-- > 0) {
let word;
let quantity;
({ quantity, mantissa, exponent, word } = illion(mantissa, exponent));
if (quantity) {
if (digits)
words.push(quantity.toString());
else
words.push(ordinal1000(quantity, options));
if (word)
words.push(word);
}
}
}
}
else {
words.push('infinity');
}
}
return words.flat().join(space);
}
function breakdown(value, groups) {
let [m, e] = value.toExponential(15).split('e'); // - 1 because toExponential returns 1 digit before the decimal point
let mantissa = m.replace('.', empty);
let exponent = Number.parseInt(e, 10); // number of digits = (exponent + 1)
const groupCount = Math.floor(exponent / 3) + 1;
if (groupCount > groups) {
const digits = (groupCount * 3) - (2 - exponent % 3);
if (digits < 16) {
([m, e] = value.toExponential(digits - 2).split('e'));
mantissa = m.replace('.', empty);
exponent = Number.parseInt(e, 10);
}
}
return { mantissa, exponent };
}
function ordinal1000(input, { and, hyphen = space }) {
const words = [];
if (input >= ONE_HUNDRED) {
words.push(ones[Math.floor(input / ONE_HUNDRED)], 'hundred');
input = input % ONE_HUNDRED;
if (and && input > ZERO)
words.push(and);
}
if (input > ZERO) {
if (input < TWENTY)
words.push(ones[input]);
else if (input % TEN === ZERO)
words.push(tens[Math.floor(input / TEN) - 2]);
else
words.push(tens[Math.floor(input / TEN) - 2] + hyphen + ones[input % TEN]);
}
return words;
}
function illion(mantissa, exponent) {
let factor = Math.floor((exponent - 3) / 3);
let quantity = 0;
switch (exponent - ((factor * 3) + 3)) {
case 0:
quantity = Number.parseInt(mantissa.slice(0, 1), 10);
mantissa = mantissa.slice(1);
exponent -= 1;
break;
case 1:
quantity = Number.parseInt(mantissa.slice(0, 2), 10);
mantissa = mantissa.slice(2);
exponent -= 2;
break;
case 2:
quantity = Number.parseInt(mantissa.slice(0, 3), 10);
mantissa = mantissa.slice(3);
exponent -= 3;
break;
}
if (factor < 0)
return { quantity, mantissa, exponent, word: null };
if (factor === 0)
return { quantity, mantissa, exponent, word: 'thousand' };
let word = 'on';
while (factor > 0) {
let a = false; // ones; use the prefixed form; tens change end from 'i' to 'a'
let s = false; // ones: tre => tres; se => ses
let x = false; // ones: tre => tres; se => sex
let m = false; // ones: septe = septem; nove => novem
let n = false; // ones: septe = septen; nove => noven
const factor0 = Math.floor(factor / 1) % 10;
const factor1 = Math.floor(factor / 10) % 10;
const factor2 = Math.floor(factor / 100) % 10;
word = `lli${word}`;
// hundreds
switch (factor2) {
case 1:
word = `centi${word}`;
a = true;
s = false;
x = true;
m = false;
n = true;
break;
case 2:
word = `ducenti${word}`;
a = true;
s = false;
x = false;
m = false;
n = true;
break;
case 3:
word = `trecenti${word}`;
a = true;
s = true;
x = false;
m = false;
n = true;
break;
case 4:
word = `quadringenti${word}`;
a = true;
s = true;
x = false;
m = false;
n = true;
break;
case 5:
word = `quingenti${word}`;
a = true;
s = true;
x = false;
m = false;
n = true;
break;
case 6:
word = `sescenti${word}`;
a = true;
s = false;
x = false;
m = false;
n = true;
break;
case 7:
word = `septingenti${word}`;
a = true;
s = false;
x = false;
m = false;
n = true;
break;
case 8:
word = `octingenti${word}`;
a = true;
s = false;
x = true;
m = true;
n = false;
break;
case 9:
word = `nongenti${word}`;
a = true;
s = false;
x = false;
m = false;
n = false;
break;
}
// tens
switch (factor1) {
case 1:
word = `deci${word}`;
a = true;
s = false;
x = false;
m = false;
n = true;
break;
case 2:
word = `viginti${word}`;
a = true;
s = true;
x = false;
m = true;
n = false;
break;
case 3:
word = (a ? 'triginta' : 'triginti') + word;
a = true;
s = true;
x = false;
m = false;
n = true;
break;
case 4:
word = (a ? 'quadraginta' : 'quadraginti') + word;
a = true;
s = true;
x = false;
m = false;
n = true;
break;
case 5:
word = (a ? 'quinquaginta' : 'quinquaginti') + word;
a = true;
s = true;
x = false;
m = false;
n = true;
break;
case 6:
word = (a ? 'sexaginta' : 'sexaginti') + word;
a = true;
s = false;
x = false;
m = false;
n = false;
break;
case 7:
word = (a ? 'septuaginta' : 'septuaginti') + word;
a = true;
s = false;
x = false;
m = false;
n = true;
break;
case 8:
word = (a ? 'octoginta' : 'octoginti') + word;
a = true;
s = false;
x = true;
m = true;
n = false;
break;
case 9:
word = (a ? 'nonaginta' : 'nonginti') + word;
a = true;
s = false;
x = false;
m = false;
n = false;
break;
}
// ones
if (a) {
switch (factor0) {
case 1:
word = `un${word}`;
break;
case 2:
word = `duo${word}`;
break;
case 3:
word = (s ? 'tres' : x ? 'tres' : 'tre') + word;
break;
case 4:
word = `quattuor${word}`;
break;
case 5:
word = `quinqua${word}`;
break;
case 6:
word = (s ? 'ses' : x ? 'sex' : 'se') + word;
break;
case 7:
word = (n ? 'septen' : m ? 'septem' : 'septe') + word;
break;
case 8:
word = `octo${word}`;
break;
case 9:
word = (n ? 'noven' : m ? 'novem' : 'nove') + word;
break;
}
}
else {
switch (factor0) {
case 0:
word = `ni${word}`;
break;
case 1:
word = `mi${word}`;
break;
case 2:
word = `bi${word}`;
break;
case 3:
word = `tri${word}`;
break;
case 4:
word = `quadri${word}`;
break;
case 5:
word = `quniti${word}`;
break;
case 6:
word = `sexti${word}`;
break;
case 7:
word = `septi${word}`;
break;
case 8:
word = `octi${word}`;
break;
case 9:
word = `noni${word}`;
break;
}
}
factor = Math.floor(factor / 1000);
}
return { quantity, mantissa, exponent, word };
}
/**
* Get the spelled out word for an exponent
*
* @remarks This is only using the exponent, There is no limit to the numbers this function can represents, however Javascript/Typescript can only represent
* numbers up to 1e308, which limits the numbers that this method can represent to 10^10^308 which is really really big.
*
* @example 6 is "million"
* @example 303 is "centillion"
* @param exponent The exponent to convert
* @returns Order of Magnitude as text
*/
export function orderOfMagnitude(exponent) {
return illion('000', exponent).word;
}
/**
* Get a short description of a number
*
* @remarks this is a shortcut to calling cardinal with options {groups: 1, digits: true}
*
* @example 1000000 "1 million"
* @example 101323847382459 "101 trillion"
*
* @param input number to convert
* @param options see {@link OptionsIllion}
* @return number as text
*/
export function summarize(input, options = {}) {
return cardinal(input, { groups: 1, digits: true, ...options });
}
export default cardinal;