Numbers are pretty awesome. People are also pretty awesome. However, the relationship between humans and numbers is fuzzy to most. We do some pretty complex stuff with numbers in our heads without even knowing it. Let's take reading numbers for instance.
464,723,104,643
If I asked you to read this number aloud, you'd do without issue. What's going on in your head when you do this? Well, a few things pop into my head.
- Well, there are 4 groups of numbers, so we'll start at billion
- Okay the first one is 4. four hundred
- The second is 6. sixty
- And lastly, 4. four
- Putting this all together, we start with four hundred sixty-four billion
I don't need to teach you how to read numbers - you all know how. But modeling this process with computer code is pretty interesting! So that's what we'll be doing today.
Now, guidelines are kind of fuzzy - but I'm hoping you can closely match my output. A lot of people get confused when to use 'and' and when to use, hyphens, etc. As usual, feel free to do whatever looks best or makes the most sense for you! However, many of you will try to match my output, which is cool too.
initech:numbers-for-people jordan$ ./num-to-human.py 9 nine initech:numbers-for-people jordan$ ./num-to-human.py 12 twelve initech:numbers-for-people jordan$ ./num-to-human.py 64 sixty-four initech:numbers-for-people jordan$ ./num-to-human.py 587 five hundred eighty-seven initech:numbers-for-people jordan$ ./num-to-human.py 590 five hundred ninety initech-2:numbers-for-people jordan$ ./num-to-human.py 10000000000 ten billion initech:numbers-for-people jordan$ ./num-to-human.py 45671 fourty-five thousand six hundred seventy-one initech:numbers-for-people jordan$ ./num-to-human.py 123456789 one hundred twenty-three million four hundred fifty-six thousand seven hundred eighty-nine initech:numbers-for-people jordan$ ./num-to-human.py 1234567890 one billion two hundred thirty-four million five hundred sixty-seven thousand eight hundred ninety initech:numbers-for-people jordan$ ./num-to-human.py 464723104643 four hundred sixty-four billion seven hundred twenty-three million one hundred four thousand six hundred forty-three initech:numbers-for-people jordan$
Be sure to save this code (why wouldn't you?) because we'll be using it for some analysis next time.
All the best,
Jordan
import math, sys def say_number(n): # Cheap way out. if n >= 10 ** 39: raise ValueError, 'Number ridiculously large' numbers = [ None, 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen', ] tens = [ None, None, 'twenty', 'thirty', 'fourty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety' ] large_names = [ None, 'thousand', 'million', 'billion', 'trillion', 'quadrillion', 'quintillion', 'sextillion', 'septillion', 'octillion', 'nonillion', 'decillion', 'undecillion' ] chunks = [] words = [] if n < 0: words.append('minus') n = -n # Split number into chunks while n != 0: chunks.append(n % 1000) n /= 1000 # Combine them with large number names: 1,234,567 => # [(1, 'million'), (234, 'thousand'), (567, None)] for n, u in reversed(zip(chunks, large_names)): # If the chunk is zero, skip it entirely if n == 0: continue # Split off hundred part, then handle the final digits: if n >= 100: words.append(numbers[n / 100]) words.append('hundred') n %= 100 # 00 is nothing, [01]x and x0 are special, xx are combined if n == 0: pass elif n < 20: words.append(numbers[n]) elif n % 10 == 0: words.append(tens[n / 10]) else: parts = tens[n / 10], numbers[n % 10] words.append('{0}-{1}'.format(*parts)) if u is not None: words.append(u) return ' '.join(words) or 'zero' for i in sys.argv[1:]: print say_number(int(i))Not quite as elegant as nooodl’s, but…
import sys def humanize_num(num): res = [] if num > 10 ** 15: raise ValueError("Only handles numbers up to 999,999,999,999,999") ones = (None, "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen") tens = (None, None, "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety") hundreds = (None, "thousand", "million", "billion", "trillion") idx = 0 # Where in the "hundreds" the chunk is while num > 0: # Extract only the three-digit chunk we're looking at sec = num % 1000 num = num // 1000 # Append the "hundreds" separator if hundreds[idx] and sec: res.append(hundreds[idx]) # Split off ones, tens, and hundreds place one = sec % 10 ten = (sec // 10) % 10 hundred = (sec // 100) % 10 if ten == 1: # We're in the "teens" res.append(ones[sec % 100]) else: if ones[one]: res.append(ones[one]) if tens[ten]: res.append(tens[ten]) if ones[hundred]: res.append("hundred and") res.append(ones[hundred]) idx += 1 if res: return " ".join(reversed(res)) else: return "zero" if __name__ == "__main__": n = int(sys.argv[1]) print humanize_num(n)First forray into c++ after hello world. :s Be advised, this thing’s REALLY big.
#include <iostream> #include <string> #include <stdexcept> //an interesting note. g++ doesn't incluide stdexcept by default for some reason, visual studio does so you don't need it in VS. using namespace std; /* A method to translate a real number and convert it into a a human readable number. @param String input A string representation of the number we're going to change over. We're using a string as that makes it number container independant and most number containers have some sort of toString capability. @returns A string representing the number that was inputted in a human format (ie. twelve, fifteen, twenty nine). This method is NOT fast, efficient, and consumes a considerably larger amount of memory. It is also much more complex, requires vigorous error checking, and is a lot bigger line wise then other solutions. What it looses is speed and effeciency however it makes up for in flexibility and modularity. It can be adapted easily with minimal refactoring to suit an application (at least I like to think so). Examples: 123456 - "one hundred twenty three thousand four hundred fifty six" 123.456 - "one hundred twnety three point four five six" 312.000 - "three hundred twelve" -0.01233 - "Negative point zero one two three three" 1.64300 - "one point six four three" It's not strictly technically how it's supposed to be read but that's how my brain handles it. Tested and confirmed to compile in visual studio 2010 express and linux mint 12. */ string convertNumberToHumanReadable(string inputNumber) { //we're using a define to deal with the problem of handling numbers larger then are scale quantifier can handle along with it's error handling. // To get this number it is (N/3 + 1) where N is (x*10^N). N must be a multple of 3 as that's how our numbering system works. +1 is needed to handle 10^1 which doesn't have a quantifier. #define scaleQuantifierNumberOfElements 22 const string baseTenNumbers[10] = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; const string nteensNumbers[10] = { "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen,", "eighteen", "nineteen" }; const string nTiesNumbers[10] = {"", "", "twenty", "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety" }; const string scaleQuantifier[scaleQuantifierNumberOfElements] = { "\b", "thousand", "million", "billion", "trillion", "quadrillion", "quintillion", "sextillion", "septillion", "octillion", "nonillion", "decillion", "undecillion", "duodecillion", "tredicillion", "quattourdecillion", "quindecillion", "sexdecillion", "septendecillion", "octodecillion", "novemdecillion", "vigintillion" }; bool isNegative = false; int decimalPoint = inputNumber.length(); //this puts the decimal point past the right most number by default which is where it would exist on an integer. //sanity checks start here, check for empty string, for negative symbol at start, and for the '.', if the number is too large for this method to handle, and if the input is just zero. if (inputNumber.length() == 0) throw runtime_error("Input is empty"); if (inputNumber.find('-') == 0) { isNegative = true; inputNumber = inputNumber.substr(1, (inputNumber.length() - 1)); } if (inputNumber.find_first_of('.') != inputNumber.find_last_of('.')) //if the first instance and the last instance don't equal each other, means more the one . characters, means not a real number throw runtime_error(inputNumber.append(" is not a real number")); else if (inputNumber.find('.') < string::npos && inputNumber.find('.') != inputNumber.length() - 1 ) decimalPoint = inputNumber.find('.'); //now is the character check. //48 = '0', 57 = '9', 46 = '.' for(int counter=0 ; counter < (int) inputNumber.length() ; counter++) { if ((inputNumber.at(counter) < 48 || inputNumber.at(counter) > 57) && inputNumber.at(counter) != '.') throw runtime_error(inputNumber.append(" is not a real number")); } if (decimalPoint > ((scaleQuantifierNumberOfElements - 1) *3)) throw runtime_error("Number is too large for this system to handle"); if ( inputNumber.length() == 1 && inputNumber.at(0) == '0' ) return "zero"; //Sanity checks are now completed. string returnedString = ""; /* //for some reason this is not trimming off non zero numbers. if(decimalPoint < (int)inputNumber.length()) inputNumber = inputNumber.substr(inputNumber.find_last_not_of('0')); This is suppose to trim of trailing zero's if there's a decimal point, since it'd be redundant to have them. It's not incorrect to leave them there technically but that's not how we would generally read it. C++ string library has the find_last_not_of method but looks like I'm still a bit muddy on how it's suppose to work. Right now all it does is trim all but the first character in a string. So I'm going to cheat for a bit and use a for loop to trim off any trailing zeros after the decimal point, at least until I can figure out just how that method is supposed to work. */ for (int counter = inputNumber.length() - 1 ; counter >= decimalPoint ; counter --) { if (inputNumber.at(counter) == '0' || inputNumber.at (counter) == '.') inputNumber = inputNumber.substr(0 ,inputNumber.length() - 1); else counter = -1; } //we handle numbers to the right of the decimal point now, creating the right half of the string. for(int counter = inputNumber.length() - 1 ; counter > decimalPoint ; counter -- ) { returnedString = string(baseTenNumbers[inputNumber.at(counter) - 48]).append(" ").append(returnedString); } if(decimalPoint < (int)inputNumber.length()) returnedString = string("point ").append(returnedString); //we appending leading 0's to the front if neccessary. By doing this we can take steps of 3 in the for loop and not worry about the < 3 cases which would require much more complex if statements. while((decimalPoint ) % 3 != 0) { inputNumber = string("0").append(inputNumber); decimalPoint++; } //now we deal with numbers to the left of the decimal point. for (int counter = decimalPoint -1 ; counter > 0 ; counter -= 3) { string tmp = ""; if (inputNumber.at(counter - 2) != '0')//we'll look at each set of numbers in a triplet going left. Look at the left most number in the triplet to determine the hundred number. tmp = string(baseTenNumbers[inputNumber.at(counter - 2) - 48]).append(" hundred "); if(inputNumber.at(counter - 1) == '1') //next is the next number, if it's 1, it's a nTeen, otherwise it's a nTies tmp = tmp.append(nteensNumbers[inputNumber.at(counter ) - 48]).append(" "); //nTies else if (inputNumber.at(counter - 1) > '1') tmp = tmp.append(nTiesNumbers[inputNumber.at(counter - 1) -48]).append(" ");\ //ones if the number before isn't a teen. if (inputNumber.at(counter) != '0' && inputNumber.at(counter - 1) != '1') tmp = tmp.append(baseTenNumbers[inputNumber.at(counter) - 48]).append(" "); //finally we append the quantifier if(tmp != "") { tmp = tmp.append(scaleQuantifier[((decimalPoint - 1) / 3) - (counter / 3)]).append(" " ); returnedString = tmp.append(returnedString); } } if(isNegative) returnedString = string("Negative ").append(returnedString); return returnedString; } int main( int argc, const char* argv[] ) { if (argc != 2 ) { cout << "Can only handle one argument. Please hang up and try your number again." << endl; return 1; } try { cout << convertNumberToHumanReadable(argv[1]) << endl; } catch (std::runtime_error &e) { cout << e.what() << endl; return 1; } }