impl/typescript.ts
2,407 bytes · the TypeScript implementation · view raw
/** Largest integer a JavaScript number holds exactly; beyond it the three languages disagree. */
const MAX_MAGNITUDE = 9007199254740991;
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"];
/** Short scale, as used in the UK since 1974: a billion is 10^9. */
const SCALES = ["", "thousand", "million", "billion", "trillion", "quadrillion"];
function belowHundred(n: number): string {
if (n < 20) return ONES[n];
const unit = n % 10;
return unit === 0 ? TENS[Math.floor(n / 10)] : `${TENS[Math.floor(n / 10)]}-${ONES[unit]}`;
}
/** One group of three digits, 1 to 999. */
function group(n: number): string {
const hundreds = Math.floor(n / 100);
const rest = n % 100;
if (hundreds === 0) return belowHundred(rest);
const head = `${ONES[hundreds]} hundred`;
return rest === 0 ? head : `${head} and ${belowHundred(rest)}`;
}
/**
* Spell out a whole number in British English words, cheque style:
* 1234 is "one thousand two hundred and thirty-four".
*/
export function integerToWords(value: number): string {
if (typeof value !== "number" || !Number.isInteger(value)) {
throw new TypeError(`integerToWords needs a whole number, received ${value}`);
}
if (value > MAX_MAGNITUDE || value < -MAX_MAGNITUDE) {
throw new RangeError(`${value} is out of range: integerToWords handles magnitudes up to ${MAX_MAGNITUDE}`);
}
if (value === 0) return "zero";
// Split into groups of three, least significant first.
const groups: number[] = [];
let remaining = Math.abs(value);
while (remaining > 0) {
groups.push(remaining % 1000);
remaining = Math.floor(remaining / 1000);
}
const words: string[] = [];
for (let i = groups.length - 1; i >= 0; i--) {
const g = groups[i];
if (g === 0) continue;
let text = group(g);
if (i > 0) text += ` ${SCALES[i]}`;
// British usage: "one thousand and one", "one million and fifty". Only the
// final group gets it, and only when it has no hundreds of its own.
else if (groups.length > 1 && g < 100) text = `and ${text}`;
words.push(text);
}
const body = words.join(" ");
return value < 0 ? `minus ${body}` : body;
}