HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptModerate

Changing Number to Words in JavaScript

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
javascriptchangingwordsnumber

Problem

One day I saw a question on Stack Overflow asking about changing numbers to words, I thought about it, and next day I started coding. When I had the code working, I thought it could, most likely, be much better. Then I started looking for some existing code about it and found this one. I wasn't able to understand much of it, but I am sure it tackles the problem much differently than I did. So I got interested about knowing other possible solutions for it.

I am new to programming and this my first "useful" code, so it would be very rewarding to have my code criticized for things that should, or could, have been done in another way.

This doesn't work with , or . (1,000 or 0.50 won't work). I am not sure why, but passing a number like (036) will return wrong results if it is a number primitive. I think it is interpreting it as an octal, but I defined the radix, so it shouldn't.

```
function numToWords(number) {

//Validates the number input and makes it a string
if (typeof number === 'string') {
number = parseInt(number, 10);
}
if (typeof number === 'number' && !isNaN(number) && isFinite(number)) {
number = number.toString(10);
}
else {
return 'This is not a valid number';
}

//Creates an array with the number's digits and
//adds the necessary amount of 0 to make it fully
//divisible by 3
var digits = number.split('');
var digitsNeeded = 3 - digits.length % 3;
if (digitsNeeded !== 3) { //prevents this : (123) ---> (000123)
while (digitsNeeded > 0) {
digits.unshift('0');
digitsNeeded--;
}
}

//Groups the digits in groups of three
var digitsGroup = [];
var numberOfGroups = digits.length / 3;
for (var i = 0; i 1) {
var posfixRange = posfix.splice(0, digitsGroupLen).reverse();
for (var m = 0; m < digitsGroupLen - 1; m++) { //'-1' prevents adding a null posfix to the last group
if(digitsGroup[m]){ // avoids 10000000 being read (one billion million)
digitsGroup[m] += ' ' + p

Solution

I want to suggest a different overall approach to this:

Your friend for this sort of stuff is the modulo operator. There's no need to treat the number as a string when breaking it apart, when a number can be broken apart with a little math.

If you've got your number (see thriggle's answer for parseInt usage), you can break it into "thousand-chunks" like so:

function chunk(number) {
  var number = 23456098325,
      thousands = [];

  while(number > 0) {
    thousands.push(number % 1000);
    number = Math.floor(number / 1000);
  }

  return thousands;
}

chunk(23456098325) // => [ 325, 98, 456, 23 ]


Note that it's "backwards": The lowest part of the number first, than the thousands, then the millions, etc..

Now, you have two tasks: Covert each chunk into English, and then add a scale (thousand, million, billion, etc.) to each of them.

For the first task, we can again use the modulo operator, since we want hundreds, tens, and single digits. The only exception is for numbers below 20, whose names don't follow the same system as the later ones.

So if you have an array of the words "one" to "nineteen", and another for the words for "twenty", "thirty", etc. up to "ninety", you can take any 1-999 number and turn it into words. And since the first bit of code breaks a large number into chunk of 1-999, that's what we need.

Final bit is to add the scale ("thousand", "million", "billion" etc.), which we can do based on the index of the chunks in the array.

So for instance, we can do this:

var ONE_TO_NINETEEN = [
  "one", "two", "three", "four", "five",
  "six", "seven", "eight", "nine", "ten",
  "eleven", "twelve", "thirteen", "fourteen", "fifteen",
  "sixteen", "seventeen", "eighteen", "nineteen"
];

var TENS = [
  "ten", "twenty", "thirty", "forty", "fifty",
  "sixty", "seventy", "eighty", "ninety"
];

var SCALES = ["thousand", "million", "billion", "trillion"];

// helper function for use with Array.filter
function isTruthy(item) {
  return !!item;
}

// convert a number into "chunks" of 0-999
function chunk(number) {
  var thousands = [];

  while(number > 0) {
    thousands.push(number % 1000);
    number = Math.floor(number / 1000);
  }

  return thousands;
}

// translate a number from 1-999 into English
function inEnglish(number) {
  var thousands, hundreds, tens, ones, words = [];

  if(number < 20) {
    return ONE_TO_NINETEEN[number - 1]; // may be undefined
  }

  if(number < 100) {
    ones = number % 10;
    tens = number / 10 | 0; // equivalent to Math.floor(number / 10)

    words.push(TENS[tens - 1]);
    words.push(inEnglish(ones));

    return words.filter(isTruthy).join("-");
  }

  hundreds = number / 100 | 0;
  words.push(inEnglish(hundreds));
  words.push("hundred");
  words.push(inEnglish(number % 100));

  return words.filter(isTruthy).join(" ");
}

// append the word for a scale. Made for use with Array.map
function appendScale(chunk, exp) {
  var scale;
  if(!chunk) {
    return null;
  }
  scale = SCALES[exp - 1];
  return [chunk, scale].filter(isTruthy).join(" ");
}


Worth noting:

-
inEnglish recurses for numbers >= 20.

-
inEnglish will return a false'y for the number zero. That's why I'm using Array.filter to remove false'y values before I join the array. For instance, the number 300 is (through some recursion) more or less constructed as [ ONE_TO_NINETEEN[3-1], "hundred", TENS[0-1], ONE_TO_NINETEEN[0-1] ]. This'll become ["three", "hundred", undefined, undefined], so we can't just join that because we'd get some trailing nonsense. So the undefined values are removed before joining.

-
I'm using Math.floor in chunk, but the bitwise-floor-trick (| 0) elsewhere. The reason is that the bitwise-floor-trick can't handle numbers larger than 2,147,483,647 (max value of a signed 32-bit integer, which is what bitwise operators work on), so it would break for large numbers. But in inEnglish we assume that the input is 0-999, so we can safely use the bitwise trick.

With that, you can take numbers and convert them to English like so:

var string = chunk(810238903242)
  .map(inEnglish)
  .map(appendScale)
  .filter(isTruthy)
  .reverse()
  .join(" ");


which yields:


eight hundred ten billion two hundred thirty-eight million nine hundred three thousand two hundred fourty-two

Of course, there are few preliminary checks you might want to make:

-
If the input number is zero return "zero".

-
If the input number is negative, you can complain to the user, or you can use Math.abs, convert the result to English like above, and prepend "negative" afterward.

-
That the input number is between Number.MIN_SAFE_INTEGER and Number.MAX_SAFE_INTEGER. Otherwise things may get weird because 64-bit floats (as all JS numbers are) can no longer accurately represent the value. Not all runtimes have those two constants though, but you can make them yourself.

Incidentally, Number.MAX_SAFE_INTEGER is nine quadrillion seven trillion one hu

Code Snippets

function chunk(number) {
  var number = 23456098325,
      thousands = [];

  while(number > 0) {
    thousands.push(number % 1000);
    number = Math.floor(number / 1000);
  }

  return thousands;
}

chunk(23456098325) // => [ 325, 98, 456, 23 ]
var ONE_TO_NINETEEN = [
  "one", "two", "three", "four", "five",
  "six", "seven", "eight", "nine", "ten",
  "eleven", "twelve", "thirteen", "fourteen", "fifteen",
  "sixteen", "seventeen", "eighteen", "nineteen"
];

var TENS = [
  "ten", "twenty", "thirty", "forty", "fifty",
  "sixty", "seventy", "eighty", "ninety"
];

var SCALES = ["thousand", "million", "billion", "trillion"];

// helper function for use with Array.filter
function isTruthy(item) {
  return !!item;
}

// convert a number into "chunks" of 0-999
function chunk(number) {
  var thousands = [];

  while(number > 0) {
    thousands.push(number % 1000);
    number = Math.floor(number / 1000);
  }

  return thousands;
}

// translate a number from 1-999 into English
function inEnglish(number) {
  var thousands, hundreds, tens, ones, words = [];

  if(number < 20) {
    return ONE_TO_NINETEEN[number - 1]; // may be undefined
  }

  if(number < 100) {
    ones = number % 10;
    tens = number / 10 | 0; // equivalent to Math.floor(number / 10)

    words.push(TENS[tens - 1]);
    words.push(inEnglish(ones));

    return words.filter(isTruthy).join("-");
  }

  hundreds = number / 100 | 0;
  words.push(inEnglish(hundreds));
  words.push("hundred");
  words.push(inEnglish(number % 100));

  return words.filter(isTruthy).join(" ");
}

// append the word for a scale. Made for use with Array.map
function appendScale(chunk, exp) {
  var scale;
  if(!chunk) {
    return null;
  }
  scale = SCALES[exp - 1];
  return [chunk, scale].filter(isTruthy).join(" ");
}
var string = chunk(810238903242)
  .map(inEnglish)
  .map(appendScale)
  .filter(isTruthy)
  .reverse()
  .join(" ");

Context

StackExchange Code Review Q#90349, answer score: 14

Revisions (0)

No revisions yet.