Number to Words
Prompt
Write a function numberToWords(num) that takes a non-negative integer and returns its English word form.
numberToWords(18653);// "eighteen thousand six hundred fifty three"numberToWords(0);// "zero"numberToWords(1000000);// "one million"The output uses lowercase words separated by single spaces. No commas, no hyphens, no "and".
Requirements
- Return the English word form of a non-negative integer
numberToWords(0)returns"zero"- Words are lowercase, separated by single spaces, with no "and"
- Support values up to the billions
Playground
Build four lookup tables first: ones (one...nine),
teens (ten...nineteen), tens (twenty...ninety),
and scales (thousand, million, billion). Everything
else plugs into these.
Write a small helper that converts any value from 0 to 999 into words. The main function can then break the input into 3-digit chunks and reuse this helper.
Walk the chunks from the lowest three digits upward. For
each chunk that is not zero, attach its scale word
(thousand, million, ...) and put the result at the
front of a list. Join the list at the end.
Solution
Explanation
The problem breaks into two parts:
- Convert any 3-digit number (0 to 999) into words.
- Walk the input in 3-digit chunks from right to left and glue the chunks together with scale words (
thousand,million,billion).
Once you see those two layers, the rest is lookup tables and a loop.
The lookup tables
English number words do not follow a single pattern. ten, eleven, twelve, and the rest of the teens are all irregular. Hardcoded tables are simpler than trying to build the words from rules.
const ONES = [ '', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine',];const TEENS = [ 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen',];const TENS = [ '', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety',];const SCALES = ['', 'thousand', 'million', 'billion'];Two small details:
ONES[0]is an empty string. That wayONES[digit]gives nothing for a leading zero and we can push it into the parts list without a special check.TENS[0]andTENS[1]are empty because the tens slot is only used for 20 through 90. Values 10 through 19 go throughTEENS.
The three-digit helper
Every 3-digit number has the same shape: an optional hundreds word, an optional tens word, and an optional ones or teens word.
function threeDigit(n) { const parts = []; if (n >= 100) { parts.push(ONES[Math.floor(n / 100)], 'hundred'); n %= 100; } if (n >= 20) { parts.push(TENS[Math.floor(n / 10)]); n %= 10; } else if (n >= 10) { parts.push(TEENS[n - 10]); n = 0; } if (n > 0) { parts.push(ONES[n]); } return parts.join(' ');}Walk through a few inputs to see how it works:
threeDigit(653)→ hundreds:six hundred, n becomes 53 → tens:fifty, n becomes 3 → ones:three. Result:"six hundred fifty three".threeDigit(18)→ skip hundreds, fall into then >= 10branch →eighteen. Result:"eighteen".threeDigit(700)→seven hundred, then n is 0. Result:"seven hundred".threeDigit(15)→fifteen. Result:"fifteen".
The main loop
Now we walk the input in chunks of 1000:
function numberToWords(num) { if (num === 0) return 'zero'; const chunks = []; let scaleIndex = 0; while (num > 0) { const chunk = num % 1000; if (chunk > 0) { const words = threeDigit(chunk); const scale = SCALES[scaleIndex]; chunks.unshift(scale ? `${words} ${scale}` : words); } num = Math.floor(num / 1000); scaleIndex++; } return chunks.join(' ');}num % 1000 pulls out the lowest three digits, Math.floor(num / 1000) drops them. Each iteration produces one chunk and scaleIndex tells us which scale word to attach. The lowest chunk has no scale (SCALES[0] is an empty string), the next one is thousand, then million, then billion.
Because we process the lowest chunk first, we use unshift to put the newest chunk at the front of the list.
Trace for 18653:
chunk = 653, scale""→threeDigit(653)is"six hundred fifty three"→ chunks:["six hundred fifty three"].chunk = 18, scale"thousand"→threeDigit(18)is"eighteen"→ chunks:["eighteen thousand", "six hundred fifty three"].numis now 0, loop ends.
Join the list: "eighteen thousand six hundred fifty three".
Why the zero case is handled up front
If num is 0, the loop condition num > 0 is false on the first check and the loop never runs. The function would return an empty string. The early return fixes that.
The if (chunk > 0) guard inside the loop is not
optional. For an input like 1_000_001 the middle chunk
is 000. If you call threeDigit(0) it returns an empty
string, and you would end up pushing a stray empty entry
into the list. Skip zero chunks and the output stays
clean.
Small extensions
- Negatives. Add one line at the top:
if (num < 0) return 'negative ' + numberToWords(-num);
- Trillions and beyond. Extend
SCALES:The loop does not change.const SCALES = ['','thousand','million','billion','trillion',];