Functional Weave
Code in Rust

text.integer-to-words@1.0.0

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;
}