Message
| This is my final, cleaned up version. It looks a bit neater in places, and handles numbers up to 10^66.
-- Convert a number to words
-- Author: Nick Gammon
-- Date: 18th March 2010
-- Examples of use:
-- words = convert_numbers_to_words ("94921277802687490518")
-- number = convert_words_to_numbers ("one hundred eight thousand three hundred nine")
-- Both functions return nil and an error message so you can check for failure,
-- or assert, eg. words = assert (convert_numbers_to_words ("2687490518"))
-- Units, must be in inverse order!
-- The trailing space is required as the space between words
local inverse_units = {
"vigintillion ", -- 10^63
"novemdecillion ", -- 10^60
"octodecillion ", -- 10^57
"septendecillion ", -- 10^54
"sexdecillion ", -- 10^51
"quindecillion ", -- 10^48
"quattuordecillion ",-- 10^45
"tredecillion ", -- 10^42
"duodecillion ", -- 10^39
"undecillion ", -- 10^36
"decillion ", -- 10^33
"nonillion ", -- 10^30
"octillion ", -- 10^27
"septillion ", -- 10^24
"sextillion ", -- 10^21
"quintillion ", -- 10^18
"quadrillion ", -- 10^15
"trillion ", -- 10^12
"billion ", -- 10^9
"million ", -- 10^6
"thousand ", -- 10^3
} -- inverse_units
local inverse_numbers = {
"one ",
"two ",
"three ",
"four ",
"five ",
"six ",
"seven ",
"eight ",
"nine ",
"ten ",
"eleven ",
"twelve ",
"thirteen ",
"fourteen ",
"fifteen ",
"sixteen ",
"seventeen ",
"eighteen ",
"nineteen ",
"twenty ",
[30] = "thirty ",
[40] = "forty ",
[50] = "fifty ",
[60] = "sixty ",
[70] = "seventy ",
[80] = "eighty ",
[90] = "ninety ",
} -- inverse_numbers
local function convert_up_to_999 (n)
if n <= 0 then
return ""
end -- if zero
local hundreds = math.floor (n / 100)
local tens = math.floor (n % 100)
local result = ""
-- if over 99 we need to say x hundred
if hundreds > 0 then
result = inverse_numbers [hundreds] .. "hundred "
if tens == 0 then
return result
end -- if only a digit in the hundreds column
-- to have "and" between things like "hundred and ten"
-- uncomment the next line
-- result = result .. "and "
end -- if
-- up to twenty it is then just five hundred (and) fifteen
if tens <= 20 then
return result .. inverse_numbers [tens]
end -- if
-- otherwise we need: thirty (something)
result = result .. inverse_numbers [math.floor (tens / 10) * 10]
-- get final digit (eg. thirty four)
local digits = math.floor (n % 10)
-- to put a hyphen between things like "forty-two"
-- uncomment the WITH HYPHEN line and
-- comment out the NO HYPHEN line
if digits > 0 then
result = result .. inverse_numbers [digits] -- NO HYPHEN
-- result = string.sub (result, 1, -2) .. "-" .. inverse_numbers [digits] -- WITH HYPHEN
end -- if
return result
end -- convert_up_to_999
-- convert a number to words
-- See: http://www.gammon.com.au/forum/?id=10155
function convert_numbers_to_words (n)
local s = tostring (n)
-- preliminary sanity checks
local c = string.match (s, "%D")
if c then
return nil, "Non-numeric digit '" .. c .. "' in number"
end -- if
if #s == 0 then
return nil, "No number supplied"
elseif #s > 66 then
return nil, "Number too big to convert to words"
end -- if
-- make multiple of 3
while #s % 3 > 0 do
s = "0" .. s
end -- while
local result = ""
local start = #inverse_units - (#s / 3) + 2
for i = start, #inverse_units do
local group = tonumber (string.sub (s, 1, 3))
if group > 0 then
result = result .. convert_up_to_999 (group) .. inverse_units [i]
end -- if not zero
s = string.sub (s, 4)
end -- for
result = result .. convert_up_to_999 (tonumber (s))
if result == "" then
result = "zero"
end -- if
return (string.gsub (result, " +$", "")) -- trim trailing spaces
end -- convert_numbers_to_words
-- Convert words to a number
-- Author: Nick Gammon
-- Date: 18th March 2010
-- Does NOT handle decimal places (eg. four point six)
local numbers = {
zero = bc.number (0),
one = bc.number (1),
two = bc.number (2),
three = bc.number (3),
four = bc.number (4),
five = bc.number (5),
six = bc.number (6),
seven = bc.number (7),
eight = bc.number (8),
nine = bc.number (9),
ten = bc.number (10),
eleven = bc.number (11),
twelve = bc.number (12),
thirteen = bc.number (13),
fourteen = bc.number (14),
fifteen = bc.number (15),
sixteen = bc.number (16),
seventeen = bc.number (17),
eighteen = bc.number (18),
nineteen = bc.number (19),
twenty = bc.number (20),
thirty = bc.number (30),
forty = bc.number (40),
fifty = bc.number (50),
sixty = bc.number (60),
seventy = bc.number (70),
eighty = bc.number (80),
ninety = bc.number (90),
} -- numbers
local units = {
hundred = bc.number ("100"),
thousand = bc.number ("1" .. string.rep ("0", 3)),
million = bc.number ("1" .. string.rep ("0", 6)),
billion = bc.number ("1" .. string.rep ("0", 9)),
trillion = bc.number ("1" .. string.rep ("0", 12)),
quadrillion = bc.number ("1" .. string.rep ("0", 15)),
quintillion = bc.number ("1" .. string.rep ("0", 18)),
sextillion = bc.number ("1" .. string.rep ("0", 21)),
septillion = bc.number ("1" .. string.rep ("0", 24)),
octillion = bc.number ("1" .. string.rep ("0", 27)),
nonillion = bc.number ("1" .. string.rep ("0", 30)),
decillion = bc.number ("1" .. string.rep ("0", 33)),
undecillion = bc.number ("1" .. string.rep ("0", 36)),
duodecillion = bc.number ("1" .. string.rep ("0", 39)),
tredecillion = bc.number ("1" .. string.rep ("0", 42)),
quattuordecillion = bc.number ("1" .. string.rep ("0", 45)),
quindecillion = bc.number ("1" .. string.rep ("0", 48)),
sexdecillion = bc.number ("1" .. string.rep ("0", 51)),
septendecillion = bc.number ("1" .. string.rep ("0", 54)),
octodecillion = bc.number ("1" .. string.rep ("0", 57)),
novemdecillion = bc.number ("1" .. string.rep ("0", 60)),
vigintillion = bc.number ("1" .. string.rep ("0", 63)),
} -- units
-- convert a number in words to a numeric form
-- See: http://www.gammon.com.au/forum/?id=10155
-- Thanks to David Haley
function convert_words_to_numbers (s)
local stack = {}
local previous_type
for word in string.gmatch (s:lower (), "[%a%d]+") do
if word ~= "and" then -- skip "and" (like "hundred and fifty two")
local top = #stack
-- If the current word is a number (English or numeric),
-- and the previous word was also a number, pop the previous number
-- from the stack and push the addition of the two numbers.
-- Otherwise, push the new number.
local number = tonumber (word) -- try for numeric (eg. 22 thousand)
if number then
number = bc.number (number) -- turn into "big number"
else
number = numbers [word]
end -- if a number-word "like: twenty"
if number then
if previous_type == "number" then -- eg. forty three
local previous_number = table.remove (stack, top) -- get the forty
number = number + previous_number -- add three
end -- if
table.insert (stack, number)
previous_type = "number"
else
-- If the current word is a unit, multiply the number on the top of the stack by the unit's magnitude.
local unit = units [word]
if not unit then
return nil, "Unexpected word: " .. word
end -- not unit
previous_type = "unit"
-- It is an error to get a unit before a number.
if top == 0 then
return nil, "Cannot have unit before a number: " .. word
end -- starts of with something like "thousand"
-- pop until we get something larger on the stack
local interim_result = bc.number (0)
while top > 0 and stack [top] < unit do
interim_result = interim_result + table.remove (stack, top)
top = #stack
end -- while
table.insert (stack, interim_result * unit)
end -- if number or not
end -- if 'and'
end -- for each word
if #stack == 0 then
return nil, "No number found"
end -- nothing
-- When the input has been parsed, sum all numbers on the stack.
local result = bc.number (0)
for _, item in ipairs (stack) do
result = result + item
end -- for
return result
end -- function convert_words_to_numbers
The test bed you can use to confirm it works is this (just put underneath and run it):
print (string.rep ("-", 75))
local start_time = GetInfo (232)
for i = 1, 5000 do
local s = ""
for i = 1, math.floor (MtRand () * 66 + 1) do
s = s .. math.floor (MtRand () * 10)
end -- for
print ("Converting: '" .. s .. "'")
local words = assert (convert_numbers_to_words (s))
print ("Result =", words)
n = assert (convert_words_to_numbers (words))
print ("Result : '" .. n:tostring () .. "'", "length =", #bc.tostring (n))
if not bc.iszero (n) then
assert (string.gsub (s, "^0+", "") == n:tostring (), "Conversion failed!")
end -- if
end -- for
local end_time = GetInfo (232)
print (string.format ("Time taken = %0.3f seconds", end_time - start_time))
The code handles up to 999 vigintillion, and has a few sanity checks on the supplied number when doing convert_numbers_to_words (eg. is it a number, is it too long).
Example output:
Converting: '221854371162103637052189353391241767991981161850420402320763181735'
Result = two hundred twenty one vigintillion eight hundred fifty four novemdecillion three hundred seventy one octodecillion one hundred sixty two septendecillion one hundred three sexdecillion six hundred thirty seven quindecillion fifty two quattuordecillion one hundred eighty nine tredecillion three hundred fifty three duodecillion three hundred ninety one undecillion two hundred forty one decillion seven hundred sixty seven nonillion nine hundred ninety one octillion nine hundred eighty one septillion one hundred sixty one sextillion eight hundred fifty quintillion four hundred twenty quadrillion four hundred two trillion three hundred twenty billion seven hundred sixty three million one hundred eighty one thousand seven hundred thirty five
Result : '221854371162103637052189353391241767991981161850420402320763181735' length = 66
The comments in the code indicate the two changes needed if you want hyphens in generated words (eg. twenty-two) or the word "and" (eg. hundred and five).
The code does not handle negative numbers or decimals. You could easily add a wrapper to do that.
The code to convert words to numbers just looks for words and digits (thus skipping hyphens). It would also skip other non-letter/non-digit things (like commas) so if you are processing untrusted input you may want to check for those yourself.
Examples of use:
print (convert_numbers_to_words ("94921277802687490518"))
(Large numbers need to be quoted, otherwise Lua turns them into scientific notation, like "9.4921277802687e+019", which it won't handle).
Result:
ninety four quintillion nine hundred twenty one quadrillion two hundred seventy seven trillion eight hundred two billion six hundred eighty seven million four hundred ninety thousand five hundred eighteen
And converting back:
print (convert_words_to_numbers ("sixty five quintillion five hundred fifty nine quadrillion eight hundred eighty seven trillion seven hundred seventy one billion one hundred sixty six million one hundred eight thousand three hundred nine"))
Result:
65559887771166108309
|
- Nick Gammon
www.gammon.com.au, www.mushclient.com | top |
|