Converting numbers from text and vice-versa

Posted by Tiopon on Wed 17 Mar 2010 05:03 AM — 56 posts, 211,481 views.

USA #0
Wondering if there's a good way to convert numbers from text (twenty) to the actual number (20). I could do this as a painful manual system, but there must be a better way than detecting everything one at a time... Best thing I've come up with so far would be something that expects commas between each set and does an addition with a looping system... search for the words "one thousand" and do tempvalue = tempvalue + 1000, etc. It would work sometimes, but it's not pretty, and it'll miss most of the more complicated scenarios...
Australia Forum Administrator #1
I think it would be complicated to get right. Why do you want to do it?

Something like this would be fiddly to do:


fifty-three million billion eighty million forty-one thousand six hundred and twenty-two.

USA #2
Nick Gammon said:
fifty-three million billion eighty million forty-one thousand six hundred and twenty-two.


53,000,000,000,080,041,622, I think?
Australia Forum Administrator #3
And the code you used to produce that, Twisol?
#4
The example Nick gave, however, is bad numbering and shouldn't be used by anyone ever.

I found code to convert numbers to names: http://rosettacode.org/wiki/Number_names#Lua

Theoretically, the reverse should be about as easy, assuming proper formatting of the input string.
USA #5
Too much data in the code on this... Ha. I'll put the other SQL examples in the next several posts.

Hmm... threw this into Google with a slightly clearer head. Shockingly there wasn't a good Lua answer. :) Lots of stuff for the reverse... most people copy Microsoft's Excel thing (http://support.microsoft.com/kb/213360) in some way or use some form of num2word to do it.

For my purposes though, it seems this Oracle forum at http://forums.oracle.com/forums/thread.jspa?threadID=506385 is likely the best result... Here's some of the code. No clue what I'll be doing to convert it back to Lua though at this point. Maybe after a lot of staring?

create or replace function word2num( p_word in varchar2 ) return number is
 type myArray is table of varchar2(255);
 
 places_arr myArray := myArray( 
'vigintillion',
'novemdecillion',
'octodecillion',
'septendecillion',
'sexdecillion',
'quindecillion',
'quattuordecillion',
'tredecillion',
'duodecillion',
'undecillion',
'decillion',
'nonillion',
'octillion',
'septillion',
'sextillion',
'quintillion',
'quadrillion',
'trillion', 'billion', 'million', 'thousand' );
 
 place_count number := places_arr.count;
 
type strarr is table of number index by varchar2(20);
hunds_arr strarr;
num_Arr strarr;
ret_num number := 0;
o_str varchar2(4000) := trim(lower(p_word)) || ' ';
temp_str varchar2(4000);
temp_num number;
 
loc number;
begin
if lower(o_str) = 'zero' then return 0; end if; 
 
for i in 1 .. 9 loop
 hunds_arr( to_Char( to_Date(i*100,'j'), 'jsp') ) := i*100;
end loop;
 
for i in 1 .. 99 loop
 num_arr( to_Char( to_Date(i,'j'), 'jsp') ) := i;
end loop;
 
for place_idx in 1 .. places_arr.count loop
 loc := instr( o_str, places_arr(place_idx) );
 if loc  0 then
  temp_num := 0;
  temp_Str := trim(substr( o_str, 1, loc-1)) || ' ';
  if hunds_arr.exists( trim(substr(temp_str,1,instr(temp_str,' ',1,2))) ) then
   temp_num := hunds_arr( trim(substr(temp_str,1,instr(temp_str,' ',1,2))) );
   temp_str := trim(substr(temp_str,instr(temp_str,' ',1,2)));
  end if;
  if num_arr.exists( trim(temp_str) ) then
   temp_num := temp_num + num_arr( trim(temp_str) );
  end if;
  ret_num := ret_num + ( power(10,3*(place_count+1-place_idx)) * temp_num );
  o_str := trim(substr( o_str, loc+length(places_arr(place_idx)) )) ||' ';
 end if;
end loop;
-- deal with <1000 portion
 temp_num := 0;
 temp_str := o_str || ' ';
  if hunds_arr.exists( trim(substr(temp_str,1,instr(temp_str,' ',1,2))) ) then
   temp_num := hunds_arr( trim(substr(temp_str,1,instr(temp_str,' ',1,2))) );
   temp_str := trim(substr(temp_str,instr(temp_str,' ',1,2)));
  end if;
  if num_arr.exists( trim(temp_str) ) then
   temp_num := temp_num + num_arr( trim(temp_str) );
  end if;
  ret_num := ret_num + temp_num;
 
 
 return (ret_num );
end;
/ 
show errors
 
 
-- test it 50 times (50 just sounded like a nice round number)
select str, num, word2num(str) from (
select num, to_Char( to_Date(num,'j'), 'Jsp') str
from (
select trunc(dbms_random.value(1,5373484)) num from dual connect by level<=50
)
);


The original version had an issue with zero, but I added their quick suggestion for a check right after it starts...
Amended on Wed 17 Mar 2010 06:25 PM by Tiopon
USA #6
Still too long... I'll split the functions. Third post has the third version.

Here is another SQL function to do the same...

create or replace procedure word_to_num(p_words IN VARCHAR2) is
  p_word varchar2(4000) := p_words;
  type mynums is table of number index by varchar2(30);
  type myquals is table of number index by varchar2(30);
  v_nums mynums;
  v_quals myquals;
  v_word VARCHAR2(30);
  v_resval NUMBER := 0;
  v_tmpresval NUMBER := 0;
  v_val    NUMBER;
  v_switch NUMBER := 0;
  FUNCTION get_word(v_str IN OUT VARCHAR2) RETURN varchar2 IS
    v_ret VARCHAR2(30);
  BEGIN
    IF INSTR(v_str,' ') = 0 THEN
      v_ret := v_str;
      v_str := '';
    ELSE
      v_ret := SUBSTR(v_str, 1, INSTR(v_str, ' ')-1);
      v_str := SUBSTR(v_str, INSTR(v_str, ' ')+1);
    END IF;
    RETURN v_ret;
  END;
begin
  v_nums('ZERO') := 0;
  v_nums('ONE') := 1;
  v_nums('TWO') := 2;
  v_nums('THREE') := 3;
  v_nums('FOUR') := 4;
  v_nums('FIVE') := 5;
  v_nums('SIX') := 6;
  v_nums('SEVEN') := 7;
  v_nums('EIGHT') := 8;
  v_nums('NINE') := 9;
  v_nums('TEN') := 10;
  v_nums('ELEVEN') := 11;
  v_nums('TWELVE') := 12;
  v_nums('THIRTEEN') := 13;
  v_nums('FOURTEEN') := 14;
  v_nums('FIFTEEN') := 15;
  v_nums('SIXTEEN') := 16;
  v_nums('SEVENTEEN') := 17;
  v_nums('EIGHTEEN') := 18;
  v_nums('NINETEEN') := 19;
  v_nums('TWENTY') := 20;
  v_nums('THIRTY') := 30;
  v_nums('FORTY') := 40;
  v_nums('FIFTY') := 50;
  v_nums('SIXTY') := 60;
  v_nums('SEVENTY') := 70;
  v_nums('EIGHTY') := 80;
  v_nums('NINETY') := 90;
  v_quals('HUNDRED') := 100;
  v_quals('THOUSAND') := 1000;
  v_quals('MILLION') := 1000000;
  v_quals('BILLION') := 1000000000;
  v_quals('TRILLION') := 1000000000000;
  v_quals('QUADRILLION') := 1000000000000000;
  v_quals('QUINTILLION') := 1000000000000000000;
  v_quals('SEXTILLION') := 1000000000000000000000;
  v_quals('SEPTILLION') := 1000000000000000000000000;
  v_quals('OCTILLION') := 1000000000000000000000000000;
  v_quals('NONILLION') := 1000000000000000000000000000000;
  v_quals('DECILLION') := 1000000000000000000000000000000000;
  v_quals('UNDECILLION') := 1000000000000000000000000000000000000;
  v_quals('DUODECILLION') := 1000000000000000000000000000000000000000;
  DBMS_OUTPUT.PUT_LINE('word     : '||p_word);
  LOOP
    EXIT WHEN p_word IS NULL;
    v_word := get_word(p_word);
    BEGIN
      v_val := v_nums(v_word);
      v_resval := v_resval + v_tmpresval;
      v_tmpresval := v_val;
    EXCEPTION
      WHEN NO_DATA_FOUND THEN
        BEGIN
          v_val := v_quals(v_word);
          IF v_val >= v_switch THEN
            v_resval := v_resval*v_val;
          END IF;
          v_tmpresval := v_tmpresval*v_val;
          v_switch := v_val;
        EXCEPTION
          WHEN NO_DATA_FOUND THEN
            DBMS_OUTPUT.PUT_LINE('Error In Number String : '||v_word);
        END;
    END;
/*    DBMS_OUTPUT.PUT_LINE('word     : '||p_word);
    DBMS_OUTPUT.PUT_LINE('tmpresval: '||TO_CHAR(v_tmpresval,'999,999,999,999,999,999,999,999,999,999,999,999,999,999'));
    DBMS_OUTPUT.PUT_LINE('resval   : '||TO_CHAR(v_resval,'999,999,999,999,999,999,999,999,999,999,999,999,999,999'));
    DBMS_OUTPUT.PUT_LINE('v_switch : '||TO_CHAR(v_switch,'999,999,999,999,999,999,999,999,999,999,999,999,999,999'));
    IF b_switch THEN
      DBMS_OUTPUT.PUT_LINE('b_switch : TRUE');
    ELSE
      DBMS_OUTPUT.PUT_LINE('b_switch : FALSE');
    END IF;
*/
  END LOOP;
  v_resval := v_resval + v_tmpresval;
  DBMS_OUTPUT.PUT_LINE(TO_CHAR(v_resval,'999,999,999,999,999,999,999,999,999,999,999,999,999,999'));
END;


Not exactly sure on that one... or the next.
Amended on Wed 17 Mar 2010 01:52 PM by Tiopon
USA #7
Two posts to this one. Still SQL.
create or replace function calculate_string(p_line in varchar2) return r is
  n number;
begin
  execute immediate 'select '||p_line||' from dual'
    into n;
  return(n);
end;
USA #8
And its SQL query...
with t as (select 'score' word,      '*20+' num from dual union all
             select 'hundred' ,        '*100+' from dual union all
             select 'thousand',        ')*1000+(' from dual union all
             select 'lakh',            ')*power(10,5)+(' from dual union all
             select 'million',         ')*power(10,6)+(' from dual union all
             select 'crore',           ')*power(10,7)+(' from dual union all
             select 'billion',         ')*power(10,9)+(' from dual union all
             select 'trillion',        ')*power(10,12)+(' from dual union all
             select 'quadrillion',     ')*power(10,15)+(' from dual union all
             select 'quintillion',     ')*power(10,18)+(' from dual union all
             select 'sextillion',      ')*power(10,21)+(' from dual union all
             select 'septillion',      ')*power(10,24)+(' from dual union all
             select 'octillion',       ')*power(10,27)+(' from dual union all
             select 'nonillion',       ')*power(10,30)+(' from dual union all
             select 'undecillion',     ')*power(10,36)+(' from dual union all
             select 'duodecillion',    ')*power(10,39)+(' from dual union all
             select 'tredecillion',    ')*power(10,42)+(' from dual union all
             select 'quattuordecillion',')*power(10,45)+(' from dual union all
             select 'quindecillion',   ')*power(10,48)+(' from dual union all
             select 'sexdecillion',    ')*power(10,51)+(' from dual union all
             select 'septendecillion', ')*power(10,54)+(' from dual union all
             select 'octodecillion',   ')*power(10,57)+(' from dual union all
             select 'novemdecillion',  ')*power(10,60)+(' from dual union all
             select 'decillion',       ')*power(10,33)+(' from dual union all
             select 'vigintillion',    ')*power(10,63)+(' from dual union all
             select 'thirteen',        '13' from dual union all
             select 'fourteen',        '14' from dual union all
             select 'fifteen',         '15' from dual union all
             select 'sixteen',         '16' from dual union all
             select 'seventeen',       '17' from dual union all
             select 'eighteen',        '18' from dual union all
             select 'nineteen',        '19' from dual union all
             select 'twenty',          '20+' from dual union all
             select 'thirty',          '30+' from dual union all
             select 'forty',          '40+' from dual union all
             select 'fifty',           '50+' from dual union all
             select 'sixty',           '60+' from dual union all
             select 'seventy',         '70+' from dual union all
             select 'eighty',          '80+' from dual union all
             select 'ninety',          '90+' from dual union all
             select 'zero',            '0+' from dual union all
             select 'one',             '1+' from dual union all
             select 'two',             '2+' from dual union all
             select 'three',           '3+' from dual union all
             select 'four',            '4+' from dual union all
             select 'five',            '5+' from dual union all
             select 'six',             '6+' from dual union all
             select 'seven',           '7+' from dual union all
             select 'eight',           '8+' from dual union all
             select 'nine',            '9+' from dual union all
             select 'ten',             '10+' from dual union all
             select 'eleven',          '11' from dual union all
             select 'twelve',          '12' from dual),
             --
  word_num as (select 'one hundred and fifty' a from dual union all
               select 'one thousand and hundred' a from dual union all
               select 'two lakhs fifty' from dual union all
               select 'three crores six lakhs fifteen' from dual union all
               select 'ONE THOUSAND - ONE HUNDRED - ELEVEN' from dual union all
               select 'Four Thousand Four Hundred' from dual union all
               select 'Five hundred and twenty seven' from dual union all
               select 'Score And Seven' from dual union all
               select  to_Char(to_Date(trunc(dbms_random.value(1,5373484)),'j'), 'Jsp') from dual union all
               select  to_Char(to_Date(trunc(dbms_random.value(1,5373484)),'j'), 'Jsp') from dual union all
               select  to_Char(to_Date(trunc(dbms_random.value(1,5373484)),'j'), 'Jsp') from dual union all
               select  to_Char(to_Date(trunc(dbms_random.value(1,5373484)),'j'), 'Jsp') from dual union all
  --end of test data
    select text,
           calculate_string(str) "number",
           str string_for_calculating
     from
      (select text,
              replace(replace(replace('('||regexp_replace(regexp_replace(a, '(power)|[[:alpha:] ]','\1'),'(\+)([^[:digit:](]|$)', '\2')||')','()*','(1)*'),'()','(0)'),'(*','(') str
         from (select *
                 from (select rownum rn, a text, regexp_replace(lower(a),'[^[:alnum:]]',' ') a from word_num)
                model
                   reference r
                     on (select rownum rn, word, num from t)
                     dimension by (rn)
                     measures(word, num)
                   main m
                    dimension by (rn dim)
                    measures (text, cast(a as varchar2(4000)) a)
                    rules iterate(1000) until(PRESENTV(r.word[iteration_number+1],1,0)<1)
                     (a[ANY]=regexp_replace(a[CV()], r.word[iteration_number+1], r.num[iteration_number+1]))
              )
           )
/ 


Anyways, I think the first would likely be the easiest to convert. Does that seem right?
USA #9
Ack, I was hoping for a nifty Lua thing and then I see this SQL monster. :-P

(One of my big issues with SQL code is that it depends so heavily on which DB server you use...)
USA #10
Nick Gammon said:

And the code you used to produce that, Twisol?


Nothing typed up. I just noticed that it always went from small to large (Fifty-three, million, billion. Eighty, million . Forty-one, thousand. Six, hundred. Twenty-two). Then I converted each "word" separately, and multiplied the words in a "sentence" together. Then I added up the sentences. (I guess "Fifty-three" would count as a sentence, so to speak, but it's more of a sub-sentence. You'd treat it as "Fifty, three" then use the result in the sentence it belonged to.)

It might not hold up in general, considering Larkin's comment, but I don't know what specifically was wrong about it. And I just ran this algorithm in my head (using Calculator to do the large-number arithmetic).
USA #11
Yeah, I agree that it doesn't look that hard, really. If you go from unit to unit, you know that the first unit is modifying the second unit. (Thousand million == thousands of millions. Billion thousand, weird as it may be: billions of thousands.)

So basically your grammar is the following:

Number := (EnglishNumber Unit*)+

EnglishNumber := One | Two | ... | Seventeen | Ninety-Nine | blablabla

Unit := Hundred | Thousand | Million | blablabla


(I guess I need "and" in my grammar but whatever <EDIT: or as Twisol says, we can just strip it and it still works>)

then once you have this parsed, you walk forward. You take the unit list, you multiply through as appropriate, then you multiply that by your English number. Finally, you sum your individual "unit components".

So, "fifty-three million billion eighty million forty-one thousand six hundred and twenty-two"

is parsed by breaking it into units like this:

fifty-three million billion --> 53 * 1,000,000 * 1,000,000,000 = 53,000,000,000,000,000
eighty million = 80 * 1,000,000 = 80,000,000
forty-one thousand = 41 * 1,000 = 41,000
six hundred = 6 * 100 = 600
and
twenty-two = 22

Finally,


  53,000,000,000,000,000
+             80,000,000
+                 41,000
+                    600
+                     22
= 53,000,000,080,041,622


This is not what Twisol got (he has an extra 000 magnitude), maybe I mixed up or he mixed up, but basically I think this isn't a super hard problem if the input grammar is fairly well-specified.


EDIT: fix tags
Amended on Wed 17 Mar 2010 05:11 PM by David Haley
USA #12
I think I must've gotten it mixed up, because that looks right. That's what I'm talking about though, yeah. I think you can ignore (strip out) "and".
USA #13
Found a VB conversion script that someone made for turning cost in Euros into numbers... biggest issue there is that it's multilingual and huge... over 18k. It was a part of a number to word conversion, and I think it's the flip side, but I get lost somewhere in the French. Anyways, does this look worth actually pursuing, or is this another number to word conversion and nothing more?

And of course, I forgot to actually include the link. :) http://visualbasic.ittoolbox.com/groups/technical-functional/visualbasic-l/convert-amount-in-words-into-amoint-in-numbers-1449021
USA #14
What exactly are you trying to do? What is your input like? The algorithm I outlined above would do the trick if your input is of the form I gave; you'd just need to add translations from English numbers to numeric values.
USA #15
The text is formatted all lower case with spaces (not hyphens) between the words. So 85 would be rendered as eighty five currently.

Making it work for me is fairly easy... I just thought making something less hack-ish would likely be useful for others in a similar way to the trigger and alias toggle aliases I posted filled some needs that hadn't been done as simply before.

Obviously in my case, I can just make it match on the words in a do while loop. Parsing two words at a time would allow for doing it fairly easily, but it's not as expandable, and anyone else who may need this in the future won't be able to use it...

Talking myself through that sentence made me ponder something... If I have a do while loop running that pulls the next word and checks it for an additive sense... so if the current word is eighty and the next word is five, consider the words together... if the following word is hundred, multiply the whole mass by 100, if it's not or it ends, just splice together the answers... Might work if it's done right. Still has possible problems with numbers like Nick mentioned, though that depends on how I choose to do the parsing. Probably have it so that if there's a modifier, it will take another larger modifier, but if the next modifier is smaller to consider it to be a new entry.

Should I pursue this, or does it sound like I just hit a major useless rabbit trail? :)

Sorry, just saw your note David... does that mean to use that, I'd need to include entries from 0-99 done in text, then it would decipher it...? How well does that work with non-hyphenated numbers, would it take eighty five thousand as 80+5000 or as 85000?
Amended on Wed 17 Mar 2010 07:59 PM by Tiopon
Australia Forum Administrator #16
No-one has mentioned it so far, so I will ...

You will need to use the "bc" library in MUSHclient (assuming this is somehow related to MUSHclient). And if it isn't, the library is available stand-alone.

The "bc" library lets you work with arbitrarily large numbers.
USA #17
I would implement this along the following lines, looping over words one at a time.

The stack is initially empty.

If the current word is a number (English or numeric), and the previous word was also a number, pop the stack and push the addition. Otherwise, push the number.

If the current word is a unit, multiply the number on the top of the stack by the unit's magnitude. (It is an error to get a unit before a number.)

When the input has been parsed, sum all numbers on the stack.

E.g...


current word             stack
fifty                    50
three                    53 <pop 50, add 3, push 53>
million                  53000000 <pop 53, *1M>
billion                  53000000000000000
eighty                   80, 53000000000000000
million                  80000000, 53000000000000000
forty                    40, 80000000, 53000000000000000
one                      41, 80000000, 53000000000000000
thousand                 41000, 80000000, 53000000000000000
six                      6, 41000, 80000000, 53000000000000000
hundred                  600, 41000, 80000000, 53000000000000000
twenty                   20, 600, 41000, 80000000, 53000000000000000
two                      22, 600, 41000, 80000000, 53000000000000000


and then you sum those numbers, and get the same result discussed previously.

The only difficulty here is in constructing the list of English number strings and their equivalence in numeric values. But since you're breaking apart values, it's not so bad: you only need the following:

one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty thirty forty fifty sixty seventy eighty ninety
Australia Forum Administrator #18
Something like this, David?

[EDIT] This is wrong. See below for better version.


[EDIT] See improved version on page 4:
http://www.gammon.com.au/forum/?id=10155&page=4




-- DON'T USE THIS -- see amended version below!!!

numbers = {
         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 

units = {
          hundred      = bc.number ("100"),
          thousand     = bc.number ("1000"),
          million      = bc.number ("1000000"),
          billion      = bc.number ("1000000000"),
          trillion     = bc.number ("1000000000000"),
          quadrillion  = bc.number ("1000000000000000"),
          quintillion  = bc.number ("1000000000000000000"),
          sextillion   = bc.number ("1000000000000000000000"),
          septillion   = bc.number ("1000000000000000000000000"),
          octillion    = bc.number ("1000000000000000000000000000"),
          nonillion    = bc.number ("1000000000000000000000000000000"),
          decillion    = bc.number ("1000000000000000000000000000000000"),
          undecillion  = bc.number ("1000000000000000000000000000000000000"),
          duodecillion = bc.number ("1000000000000000000000000000000000000000"),
  } -- 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 (s)

  local stack = {}
  local previous_type
  
  s = string.gsub (s:lower (), "%-", " ")  -- convert hyphens to spaces
  
  for word in string.gmatch (s, "%a+") 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 stack and push the addition. 

      -- Otherwise, push the number.

      local number = numbers [word]
      if number then
        if previous_type == "number" then   -- eg. forty three
          local previous_number = table.remove (stack, top)  -- get the three
          number = number + previous_number  -- add forty
        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"
        stack [top] = stack [top] * 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

test = " fifty-three million billion eighty million forty-one thousand six hundred and twenty-two"

n = assert (convert (test))
print ("Result =", n, "length =", #bc.tostring (n))


Output:


Result = 53000000080041622 length = 17


You could put alternatives in the units list above or add others as you require (eg. septdecillion, septendecillion). You might want to add plurals (eg. "hundreds", "thousands").

Note that this algorithm does not handle decimal places. Nor has it been extensively tested.
Amended on Thu 18 Mar 2010 10:05 PM by Nick Gammon
Australia Forum Administrator #19
It fails on:

"two trillion four hundred thirty-two billion eight hundred seventy-six million three hundred forty-five thousand nine hundred twenty-three"

It should give: 2,432,876,345,923

But actually gives: 2,032,076,047,423

So don't use it yet.

(It will also fail on "zero").
Australia Forum Administrator #20
This is the problem:


Converting: two trillion four hundred thirty-two billion eight hundred seventy-six million three hundred forty-five thousand nine hundred twenty-three



Processing word:        two stack is: 2
Processing word:   trillion stack is: 2000000000000
Processing word:       four stack is: 2000000000000, 4
Processing word:    hundred stack is: 2000000000000, 400
Processing word:     thirty stack is: 2000000000000, 400, 30
Processing word:        two stack is: 2000000000000, 400, 32
Processing word:    billion stack is: 2000000000000, 400, 32000000000
Processing word:      eight stack is: 2000000000000, 400, 32000000000, 8
Processing word:    hundred stack is: 2000000000000, 400, 32000000000, 800
Processing word:    seventy stack is: 2000000000000, 400, 32000000000, 800, 70
Processing word:        six stack is: 2000000000000, 400, 32000000000, 800, 76
Processing word:    million stack is: 2000000000000, 400, 32000000000, 800, 76000000
Processing word:      three stack is: 2000000000000, 400, 32000000000, 800, 76000000, 3
Processing word:    hundred stack is: 2000000000000, 400, 32000000000, 800, 76000000, 300
Processing word:      forty stack is: 2000000000000, 400, 32000000000, 800, 76000000, 300, 40
Processing word:       five stack is: 2000000000000, 400, 32000000000, 800, 76000000, 300, 45
Processing word:   thousand stack is: 2000000000000, 400, 32000000000, 800, 76000000, 300, 45000
Processing word:       nine stack is: 2000000000000, 400, 32000000000, 800, 76000000, 300, 45000, 9
Processing word:    hundred stack is: 2000000000000, 400, 32000000000, 800, 76000000, 300, 45000, 900
Processing word:     twenty stack is: 2000000000000, 400, 32000000000, 800, 76000000, 300, 45000, 900, 20
Processing word:      three stack is: 2000000000000, 400, 32000000000, 800, 76000000, 300, 45000, 900, 23
Result = 2032076047423 length = 13


Processing "four hundred thirty-two billion" it is not associating the "four hundred" with the "thirty-two billion".
Amended on Wed 17 Mar 2010 10:30 PM by Nick Gammon
Australia Forum Administrator #21
OK I think I have it. This is better:

[EDIT] Amended to allow for "real" numbers (eg. "42 million 500 thousand")


[EDIT] Amended to allow for the input zero.

[EDIT] See improved version on page 4:
http://www.gammon.com.au/forum/?id=10155&page=4



-- 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 ("1000"),
          million      = bc.number ("1000000"),
          billion      = bc.number ("1000000000"),
          trillion     = bc.number ("1000000000000"),
          quadrillion  = bc.number ("1000000000000000"),
          quintillion  = bc.number ("1000000000000000000"),
          sextillion   = bc.number ("1000000000000000000000"),
          septillion   = bc.number ("1000000000000000000000000"),
          octillion    = bc.number ("1000000000000000000000000000"),
          nonillion    = bc.number ("1000000000000000000000000000000"),
          decillion    = bc.number ("1000000000000000000000000000000000"),
          undecillion  = bc.number ("1000000000000000000000000000000000000"),
          duodecillion = bc.number ("1000000000000000000000000000000000000000"),
  } -- 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 stack and push the addition. 
      -- Otherwise, push the 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 three
          number = number + previous_number  -- add forty
        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


tests = { [bc.number ("2432876345923")] = "two trillion four hundred thirty-two billion eight hundred seventy-six million three hundred forty-five thousand nine hundred twenty-three", [bc.number ("44240592621")] = "forty-four billion two hundred forty million five hundred ninety-two thousand six hundred twenty-one", [bc.number ("277198501535")] = "two hundred seventy-seven billion one hundred ninety-eight million five hundred one thousand five hundred thirty-five", [bc.number ("477708080833")] = "four hundred seventy-seven billion seven hundred eight million eighty thousand eight hundred thirty-three", [bc.number ("67707562873705")] = "sixty-seven trillion seven hundred seven billion five hundred sixty-two million eight hundred seventy-three thousand seven hundred five", [bc.number ("67707562873705")] = "67 trillion 7 hundred seven billion 5 hundred 62 million eight hundred 73 thousand 705", } for number, words in pairs (tests) do print ("Converting: ", words) n = assert (convert_words_to_numbers (words)) assert (number == n, "Conversion failed!") print ("Result =", n, "length =", #bc.tostring (n)) end -- for


Output:


Converting:  sixty-seven trillion seven hundred seven billion five hundred sixty-two million eight hundred seventy-three thousand seven hundred five
Result = 67707562873705 length = 14
Converting:  forty-four billion two hundred forty million five hundred ninety-two thousand six hundred twenty-one
Result = 44240592621 length = 11
Converting:  two trillion four hundred thirty-two billion eight hundred seventy-six million three hundred forty-five thousand nine hundred twenty-three
Result = 2432876345923 length = 13
Converting:  67 trillion 7 hundred seven billion 5 hundred 62 million eight hundred 73 thousand 705
Result = 67707562873705 length = 14
Converting:  two hundred seventy-seven billion one hundred ninety-eight million five hundred one thousand five hundred thirty-five
Result = 277198501535 length = 12
Converting:  four hundred seventy-seven billion seven hundred eight million eighty thousand eight hundred thirty-three
Result = 477708080833 length = 12


When the "units" arrive (eg. billion) you don't just pop the last number, you pop until you get something larger than a billion.

So in the example earlier, "four hundred thirty-two billion" ... when you get the "billion" you keep popping (and adding) until we reach the "two trillion" number, which is larger than the "billion" unit.
Amended on Thu 18 Mar 2010 10:06 PM by Nick Gammon
Australia Forum Administrator #22
Maybe the input "zero" is a special case, after all you don't normally say "twenty-two and zero".
USA #23
I just put zero at the top of the listed numbers, above one... so the top section looks like:
local numbers = {
         zero       = bc.number (0),
         one        = bc.number (1),
         two        = bc.number (2),
         three      = bc.number (3),
and that seems to work properly for those cases... any possible caveats on that?
Australia Forum Administrator #24
Oh well, if it works, it works.

I amended by post to have that in it.
Australia Forum Administrator #25
For completeness, I did the inverse function as well.

[EDIT] See improved version on page 4:
http://www.gammon.com.au/forum/?id=10155&page=4



-- Convert a number to words
-- Author: Nick Gammon
-- Date: 18th March 2010

local inverse_units = {
  {  len = #"1000000000000000000000000000000000000000",    word = "duodecillion " },
  {  len = #"1000000000000000000000000000000000000",       word = "undecillion " },
  {  len = #"1000000000000000000000000000000000",          word = "decillion " },
  {  len = #"1000000000000000000000000000000",             word = "nonillion " },
  {  len = #"1000000000000000000000000000",                word = "octillion " },
  {  len = #"1000000000000000000000000",                   word = "septillion " },
  {  len = #"1000000000000000000000",                      word = "sextillion " },
  {  len = #"1000000000000000000",                         word = "quintillion " },
  {  len = #"1000000000000000",                            word = "quadrillion " },
  {  len = #"1000000000000",                               word = "trillion " },
  {  len = #"1000000000",                                  word = "billion " },
  {  len = #"1000000",                                     word = "million " },
  {  len = #"1000",                                        word = "thousand " },
  } -- 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
 

-- convert a number in words to a numeric form
-- See: http://www.gammon.com.au/forum/?id=10155

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 "
  end -- if
  
  if tens == 0 then
    return result
  end -- if only a digit in the hundreds column
  
  -- 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)

  if digits > 0 then
    result = result ..  inverse_numbers [digits]
  end -- if 
  
  return result
  
end -- convert_up_to_999

function convert_numbers_to_words (n)
  local s = tostring (n)
  -- 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].word
    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 Trim (result)

end -- convert_numbers_to_words


tests = { [bc.number ("542")] = "five hundred forty two", [bc.number ("2432876345923")] = "two trillion four hundred thirty two billion eight hundred seventy six million three hundred forty five thousand nine hundred twenty three", [bc.number ("44240592621")] = "forty four billion two hundred forty million five hundred ninety two thousand six hundred twenty one", [bc.number ("277198501535")] = "two hundred seventy seven billion one hundred ninety eight million five hundred one thousand five hundred thirty five", [bc.number ("477708080833")] = "four hundred seventy seven billion seven hundred eight million eighty thousand eight hundred thirty three", [bc.number ("67707562873705")] = "sixty seven trillion seven hundred seven billion five hundred sixty two million eight hundred seventy three thousand seven hundred five", } for number, words in pairs (tests) do print ("Converting: ", number) w = assert (convert_numbers_to_words (number)) print ("Result = ", w) print ("Expected = ", words) assert (words == w, "Conversion failed!") end -- for


Test results:


Converting:  477708080833
Result   =  four hundred seventy seven billion seven hundred eight million eighty thousand eight hundred thirty three
Expected =  four hundred seventy seven billion seven hundred eight million eighty thousand eight hundred thirty three
Converting:  2432876345923
Result   =  two trillion four hundred thirty two billion eight hundred seventy six million three hundred forty five thousand nine hundred twenty three
Expected =  two trillion four hundred thirty two billion eight hundred seventy six million three hundred forty five thousand nine hundred twenty three
Converting:  277198501535
Result   =  two hundred seventy seven billion one hundred ninety eight million five hundred one thousand five hundred thirty five
Expected =  two hundred seventy seven billion one hundred ninety eight million five hundred one thousand five hundred thirty five
Converting:  67707562873705
Result   =  sixty seven trillion seven hundred seven billion five hundred sixty two million eight hundred seventy three thousand seven hundred five
Expected =  sixty seven trillion seven hundred seven billion five hundred sixty two million eight hundred seventy three thousand seven hundred five
Converting:  542
Result   =  five hundred forty two
Expected =  five hundred forty two
Converting:  44240592621
Result   =  forty four billion two hundred forty million five hundred ninety two thousand six hundred twenty one
Expected =  forty four billion two hundred forty million five hundred ninety two thousand six hundred twenty one
Amended on Thu 18 Mar 2010 10:06 PM by Nick Gammon
Australia Forum Administrator #26
And if you put both lots of functions together, and make a little test driver:


print (string.rep ("-", 75))

for i = 1, 5000 do
  local s = ""
  for i = 1, math.floor (MtRand () * 40 + 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



That ran to completion and demonstrated that 5000 randomly-generated numbers could be converted into words and then back again to the same number.

A small sample of its output:



Converting: '1191160927'
Result = one billion one hundred ninety one million one hundred sixty thousand nine hundred twenty seven
Result : '1191160927' length = 10
Converting: '87158213994542611069177258822761676523'
Result = eighty seven undecillion one hundred fifty eight decillion two hundred thirteen nonillion nine hundred ninety four octillion five hundred forty two septillion six hundred eleven sextillion sixty nine quintillion one hundred seventy seven quadrillion two hundred fifty eight trillion eight hundred twenty two billion seven hundred sixty one million six hundred seventy six thousand five hundred twenty three
Result : '87158213994542611069177258822761676523' length = 38
Converting: '432153673017442616601652256641810'
Result = four hundred thirty two nonillion one hundred fifty three octillion six hundred seventy three septillion seventeen sextillion four hundred forty two quintillion six hundred sixteen quadrillion six hundred one trillion six hundred fifty two billion two hundred fifty six million six hundred forty one thousand eight hundred ten
Result : '432153673017442616601652256641810' length = 33
Converting: '3651996842759435382125'
Result = three sextillion six hundred fifty one quintillion nine hundred ninety six quadrillion eight hundred forty two trillion seven hundred fifty nine billion four hundred thirty five million three hundred eighty two thousand one hundred twenty five
Result : '3651996842759435382125' length = 22
Converting: '99222052564443194'
Result = ninety nine quadrillion two hundred twenty two trillion fifty two billion five hundred sixty four million four hundred forty three thousand one hundred ninety four
Result : '99222052564443194' length = 17
Converting: '4'
Result = four
Result : '4' length = 1
Converting: '23514160057930735976287957833'
Result = twenty three octillion five hundred fourteen septillion one hundred sixty sextillion fifty seven quintillion nine hundred thirty quadrillion seven hundred thirty five trillion nine hundred seventy six billion two hundred eighty seven million nine hundred fifty seven thousand eight hundred thirty three
Result : '23514160057930735976287957833' length = 29
Converting: '9066868777537465942113528'
Result = nine septillion sixty six sextillion eight hundred sixty eight quintillion seven hundred seventy seven quadrillion five hundred thirty seven trillion four hundred sixty five billion nine hundred forty two million one hundred thirteen thousand five hundred twenty eight
Result : '9066868777537465942113528' length = 25
Converting: '327200486'
Result = three hundred twenty seven million two hundred thousand four hundred eighty six
Result : '327200486' length = 9
Converting: '587231717506879941619'
Result = five hundred eighty seven quintillion two hundred thirty one quadrillion seven hundred seventeen trillion five hundred six billion eight hundred seventy nine million nine hundred forty one thousand six hundred nineteen
Result : '587231717506879941619' length = 21
Converting: '80'
Result = eighty
Result : '80' length = 2
Converting: '5621235270740331495746400597814797'
Result = five decillion six hundred twenty one nonillion two hundred thirty five octillion two hundred seventy septillion seven hundred forty sextillion three hundred thirty one quintillion four hundred ninety five quadrillion seven hundred forty six trillion four hundred billion five hundred ninety seven million eight hundred fourteen thousand seven hundred ninety seven
Result : '5621235270740331495746400597814797' length = 34
Converting: '1792339879271350525'
Result = one quintillion seven hundred ninety two quadrillion three hundred thirty nine trillion eight hundred seventy nine billion two hundred seventy one million three hundred fifty thousand five hundred twenty five
Result : '1792339879271350525' length = 19
Converting: '8947516767792286447107637168487510769000'
Result = eight duodecillion nine hundred forty seven undecillion five hundred sixteen decillion seven hundred sixty seven nonillion seven hundred ninety two octillion two hundred eighty six septillion four hundred forty seven sextillion one hundred seven quintillion six hundred thirty seven quadrillion one hundred sixty eight trillion four hundred eighty seven billion five hundred ten million seven hundred sixty nine thousand
Result : '8947516767792286447107637168487510769000' length = 40

USA #27
I may be late to the party (it's St. Patrick's day. I'm in Boston. So.... Pub crawl!)

But here's a function in LPEG that I did.


...Code's on Page 3. I'm tired of editing this post :),,,


This doesn't do decimals. It will do stupid wordings of numbers too!

WordtoNum("one hundred seven") -> 107
WordtoNum("seven hundred thirty one thousand nine hundred forty six") -> 731946
WordtoNum("four thousand twelve hundred") -> 5200
Amended on Thu 18 Mar 2010 03:40 AM by WillFa
Australia Forum Administrator #28

/print (WordtoNum  "eight hundred sixty six million five hundred thirty six thousand nine hundred eighteen")

--> 806


Afraid not.

Your solution looks elegant though. I thought an LPEG solution could be made to work.
Australia Forum Administrator #29
If you want the word "and" in there, and hyphens between "forty" and "two" (ie. forty-two) change convert_up_to_999 to read:


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
  
    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)

  if digits > 0 then
    result = string.sub (result, 1, -2) .. "-" ..  inverse_numbers [digits]
  end -- if 
  
  return result
  
end -- convert_up_to_999


eg.


Converting: 67707562873705

Result = sixty-seven trillion seven hundred and seven billion five hundred and sixty-two million eight hundred and seventy-three thousand seven hundred and five
USA #30
fixed the code. It was an ordering problem. LPEG was matching "six" and then ending at the "ty" in sixty. Fixed the order. Editted code above works with


print(WordtoNum("two trillion four hundred thirty two billion eight hundred seventy six million three hundred forty five thousand nine hundred twenty three")) --> 2432876345923



p.s. did I mention Pub Crawl? ;)

code copied since we're on a new page:


function WordtoNum (str)
	local P, V, Cf, C, Cg, Cc = lpeg.P, lpeg.V, lpeg.Cf, lpeg.C, lpeg.Cg, lpeg.Cc
	lpeg.locale(lpeg)
	local numvalues = {  one = 1, two = 2, three = 3, four = 4, five = 5,
				six = 6, seven = 7, eight = 8, nine= 9, ten = 10,
				eleven = 11, twelve = 12, thirteen = 13, fourteen = 14, fifteen = 15,
				sixteen = 16, seventeen = 17, eighteen = 18, nineteen = 19, twenty = 20,
				thirty = 30, forty = 40, fifty = 50, sixty = 60, seventy = 70, eighty = 80,
				ninety = 90, hundred = 10^2,
				thousand = 10^3,
				million = 10^6,
				billion = 10^9,
				trillion = 10^12,
				quadrillion = 10^15,
				--blah blah
				}

	local ndigit = P"twenty" + P"thirty" + P"forty" + P"fifty" + P"sixty" + P"seventy" + P"eighty" + P"ninety" +
			P'hundred' +
			P"ten" + P"eleven" + P"twelve" + P"thirteen" + P"fourteen" + P"fifteen" +
			P"sixteen" + P"seventeen" + P"eighteen" + P"nineteen" +
			P"one" + P"two" + P"three" + P"four" + P"five" + P"six" + P"seven" + P"eight" + P"nine"


	local powers =   P'vigintillion' + P'novemdecillion' + P'octodecillion' + P'septendecillion' +
				P'sexdecillion' + P'quindecillion' + P'quattuordecillion' + P'tredecillion' +
				P'duodecillion' + P'undecillion' + P'decillion' + P'nonillion' + P'octillion' +
				P'septillion' + P'sextillion' + P'quintillion' + P'quadrillion' + P'trillion' +
				P'billion' + P'million' + P'thousand'

	local function SumVals (seed, ...)
		local cypher = {...}
		local tmp = 0

		for x = 1, #cypher do
			if numvalues[cypher[x]] == 100 then
				if tmp == 0 then
					tmp = 100
				else
					tmp = tmp * 100
				end
			elseif powers:match(cypher[x] or "") then
				tmp = tmp * numvalues[cypher[x]]
			else
				tmp = tmp + numvalues[cypher[x]]
			end
		end
		return seed + tmp
	end

	local cypher = Cg( (C(ndigit) * lpeg.space^-1)^1 * ((C(powers) * lpeg.space^-1) + P(0)) )
	local number = Cf(Cc(0) * cypher^1, SumVals)

	return number:match(str)
end

Amended on Thu 18 Mar 2010 03:38 AM by WillFa
Australia Forum Administrator #31
Is that the latest code? I get:


[string "Immediate"]:41: bad argument #1 to 'match' (string expected, got nil)
stack traceback:
	[C]: in function 'match'
	[string "Immediate"]:41: in function <[string "Immediate"]:30>
	[C]: in function 'match'
	[string "Immediate"]:53: in function 'WordtoNum'
	[string "Immediate"]:56: in main chunk

USA #32
oops, add or "" to the powers:match line.

editted above.

I think; what's the string you tested?

the other thing is the numvalues table doesn't contain all the powers of 10 for stuff like duodecaooberzillion.
Amended on Thu 18 Mar 2010 03:22 AM by WillFa
Australia Forum Administrator #33
Tiopon said:

I just put zero at the top of the listed numbers, above one... so the top section looks like:
...
and that seems to work properly for those cases... any possible caveats on that?


With that in it accepts "zero", and also constructs like "zero thousand", "zero hundred and twenty two" or "five hundred and zero" which I suppose is OK. It isn't wrong, it just looks strange.
Australia Forum Administrator #34
WillFa said:

oops, add or "" to the powers:match line.

editted above.

I think; what's the string you tested?


"Your" number:


print(WordtoNum("two trillion four hundred thirty two billion eight hundred seventy six million three hundred forty five thousand nine hundred twenty three")) --> 2432876345923


Still doing it:


[string "Immediate"]:44: attempt to perform arithmetic on field '?' (a nil value)
stack traceback:
	[string "Immediate"]:44: in function <[string "Immediate"]:30>
	[C]: in function 'match'
	[string "Immediate"]:53: in function 'WordtoNum'
	[string "Immediate"]:57: in main chunk

USA #35
This is arguably a good place to use the "be liberal in what you receive, and conservative in what you emit" philosophy. Accepting "zero" was simple to add (seemingly), and doesn't hurt the output.

Bonus points for converting "the answer to life, the universe, and everything" to 42.
Australia Forum Administrator #36
Thank you Twisol. That is a number that is never far from the front of my mind. :P
USA #37
Edit on this page is correct... I changed my for counter from i to x so I wouldn't have to deal with escaping the brackets in the code, but actually forgot to change the for line...

for i=...
   cypher[x]


silly goof.
Amended on Thu 18 Mar 2010 03:29 AM by WillFa
Australia Forum Administrator #38
Willfa, if you don't work the bc library into your solution, it is eventually going to give inaccurate results. I suppose those results will be acceptable if they are going to be converted to a "double" anyway, but why not let the caller do that?
USA #39
Nick Gammon said:

Willfa, if you don't work the bc library into your solution, it is eventually going to give inaccurate results. I suppose those results will be acceptable if they are going to be converted to a "double" anyway, but why not let the caller do that?


that can/should be done in the numvalues table, right? at the moment, it'll parse "five quintillion" but there's no corresponding quintillion = 10^18 in the numvalues table (so the function will error)...

quintillion = bc.number(10^18)
(etc) would solve the issue, right?
Amended on Thu 18 Mar 2010 03:34 AM by WillFa
Australia Forum Administrator #40


print(WordtoNum("seven billion nine hundred seventeen million seven hundred sixty one thousand six hundred fifty eight"))

--> 7000000907

Australia Forum Administrator #41
WillFa said:


quintillion = bc.number(10^18)
(etc) would solve the issue, right?


Quite possibly in principle, however you probably need to do all the arithmetic in bignums, or you get type conversion problems.
USA #42
Nick Gammon said:



print(WordtoNum("seven billion nine hundred seventeen million seven hundred sixty one thousand six hundred fifty eight"))

--> 7000000907




Fixed. Same "seven" .. "ty", "seven" .. "teen" problem as before.
Amended on Thu 18 Mar 2010 03:40 AM by WillFa
Australia Forum Administrator #43
Try my test bed:


print (string.rep ("-", 75))

for i = 1, 5000 do
  local s = ""
  for i = 1, math.floor (MtRand () * 10 + 1) do
    s = s .. math.floor (MtRand () * 10)
  end -- for
  
  print ("Converting: '" .. s .. "'")
  local words = assert (convert_numbers_to_words (s))
  print ("Result =", words)

  n2 = WordtoNum (words)
  print ("LPEG result was:", n2)

  assert (tostring (n2) == string.gsub (s, "^0+", ""), "LPEG conversion failed!")
end -- for



For example:


Converting: '2516464739'
Result = two billion five hundred sixteen million four hundred sixty four thousand seven hundred thirty nine
LPEG result was: 2000000506

or

Converting: '883743219'
Result = eight hundred eighty three million seven hundred forty three thousand two hundred nineteen
LPEG result was: 883743209


USA #44
you don't have the newest code on this page.

Looking at the "506" at the end of the number, it matched "six" before "sixteen" and the remaining "teen million..." in the string didn't fit the LPEG pattern so it stopped parsing.

Your "local ndigit =" lines are out of date.
Australia Forum Administrator #45
It seems to work OK now, excepting zero.

Your code (for 5000 iterations): 4.011 seconds
My code (for 5000 iteration, however different): 3.723 seconds

So they are comparable in speed.

I had to cut the size of the test number down:


  for i = 1, math.floor (MtRand () * 14 + 1) do
    s = s .. math.floor (MtRand () * 10)
  end -- for


Otherwise you get:


seven hundred thirteen trillion six hundred thirty one billion five hundred fifty four million six hundred five thousand eight hundred ninety two

LPEG result was: 7.1363155460589e+014

USA #46
Nick Gammon said:

Try my test bed:


print (string.rep ("-", 75))

for i = 1, 5000 do
  local s = ""
  for i = 1, math.floor (MtRand () * 10 + 1) do
    s = s .. math.floor (MtRand () * 10)
  end -- for
  if tonumber(s) ~= 0 then
  print ("Converting: '" .. s .. "'")
  local words = assert (convert_numbers_to_words (s))
  print ("Result =", words)

  n2 = WordtoNum (words)
  print ("LPEG result was:", n2)

  assert (tostring (n2) == string.gsub (s, "^0+", ""), "LPEG conversion failed!")
  end -- if
end -- for




slightly modified since my LPEG doesn't like "zero", and your test strings can be "000" which fail the assert.
USA #47
Nick Gammon said:


LPEG result was: 7.1363155460589e+014



Do you lose precision (the final 2 ones), or is it the limitation of display in scientific notation?




print(string.format("%i", WordtoNum("seven hundred thirteen trillion six hundred thirty one billion five hundred fifty four million six hundred five thousand eight hundred ninety two")))


shows full precision.
Amended on Thu 18 Mar 2010 04:11 AM by WillFa
Australia Forum Administrator #48
It's the scientific notation - I am checking for an exact string match. Anyway, this is your version amended to work with the bc library:


function WordtoNum (str)
  local P, V, Cf, C, Cg, Cc = lpeg.P, lpeg.V, lpeg.Cf, lpeg.C, lpeg.Cg, lpeg.Cc
  lpeg.locale(lpeg)
  local numvalues = {  one = 1, two = 2, three = 3, four = 4, five = 5,
        six = 6, seven = 7, eight = 8, nine= 9, ten = 10,
        eleven = 11, twelve = 12, thirteen = 13, fourteen = 14, fifteen = 15,
        sixteen = 16, seventeen = 17, eighteen = 18, nineteen = 19, twenty = 20,
        thirty = 30, forty = 40, fifty = 50, sixty = 60, seventy = 70, eighty = 80,
        ninety = 90, hundred = 10^2,
        thousand     = bc.number ("1000"),
        million      = bc.number ("1000000"),
        billion      = bc.number ("1000000000"),
        trillion     = bc.number ("1000000000000"),
        quadrillion  = bc.number ("1000000000000000"),
        quintillion  = bc.number ("1000000000000000000"),
        sextillion   = bc.number ("1000000000000000000000"),
        septillion   = bc.number ("1000000000000000000000000"),
        octillion    = bc.number ("1000000000000000000000000000"),
        nonillion    = bc.number ("1000000000000000000000000000000"),
        decillion    = bc.number ("1000000000000000000000000000000000"),
        undecillion  = bc.number ("1000000000000000000000000000000000000"),
        duodecillion = bc.number ("1000000000000000000000000000000000000000"),


        --blah blah
        }

  local ndigit = P"twenty" + P"thirty" + P"forty" + P"fifty" + P"sixty" + P"seventy" + P"eighty" + P"ninety" +
      P'hundred' +
      P"ten" + P"eleven" + P"twelve" + P"thirteen" + P"fourteen" + P"fifteen" +
      P"sixteen" + P"seventeen" + P"eighteen" + P"nineteen" +
      P"one" + P"two" + P"three" + P"four" + P"five" + P"six" + P"seven" + P"eight" + P"nine"


  local powers =   P'vigintillion' + P'novemdecillion' + P'octodecillion' + P'septendecillion' +
        P'sexdecillion' + P'quindecillion' + P'quattuordecillion' + P'tredecillion' +
        P'duodecillion' + P'undecillion' + P'decillion' + P'nonillion' + P'octillion' +
        P'septillion' + P'sextillion' + P'quintillion' + P'quadrillion' + P'trillion' +
        P'billion' + P'million' + P'thousand'

  local function SumVals (seed, ...)
    local cypher = {...}
    local tmp = bc.number (0)

    for x = 1, #cypher do
      if numvalues[cypher[x]] == 100 then
        if tmp == 0 then
          tmp = 100
        else
          tmp = tmp * 100
        end
      elseif powers:match(cypher[x] or "") then
        tmp = tmp * numvalues[cypher[x]]
      else
        tmp = tmp + numvalues[cypher[x]]
      end
    end
    return seed + tmp
  end

  local cypher = Cg( (C(ndigit) * lpeg.space^-1)^1 * ((C(powers) * lpeg.space^-1) + P(0)) )
  local number = Cf(Cc(0) * cypher^1, SumVals)

  return number:match(str)
end

USA #49
Nick Gammon said:

Tiopon said:

I just put zero at the top of the listed numbers, above one... so the top section looks like:
...
and that seems to work properly for those cases... any possible caveats on that?


With that in it accepts "zero", and also constructs like "zero thousand", "zero hundred and twenty two" or "five hundred and zero" which I suppose is OK. It isn't wrong, it just looks strange.
Yeah... doing stuff like eight million zero thousand two hundred and five... does convert to 8000205. Odd, but it may end up getting parsed at some point.
Australia Forum Administrator #50
I'm hoping that duodecillion is high enough for the use you are planning to put it to in a MUD game.

Something like:


The large, black wolf brushes you.
You _maim_ the large, black wolf!
The large, black wolf brushes you.
You dodge the large, black wolf's attack.
You _demolish_ the large, black wolf!
The large, black wolf is DEAD!!
You receive six octillion two hundred and twenty-four septillion two hundred and forty-six sextillion seven hundred and thirty-two quintillion five hundred and twenty-six quadrillion eight hundred and ninety-one trillion nine hundred and sixty-two billion seven hundred and thirty million five hundred and thirty-five thousand one hundred and forty experience points.
The large, black wolf's guts spill grotesquely from its torso.
You gain a level!!!
Amended on Thu 18 Mar 2010 04:17 AM by Nick Gammon
USA #51
Par for the course for DBZ games. :-P
I'll have to read over the code in more detail soon, at the moment it's late and I'm pissed off because somebody stole my gym bag from a bar. Sigh.
Australia Forum Administrator #52
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

Amended on Fri 19 Mar 2010 03:17 AM by Nick Gammon
USA #53
Beautiful and far beyond what I dreamed when I asked. Thanks so much... convert.lua is making me very happy. :)
USA #54
Nick Gammon said:
      if number then
        if previous_type == "number" then   -- eg. forty three
          local previous_number = table.remove (stack, top)  -- get the three
          number = number + previous_number  -- add forty
        end -- if 
        table.insert (stack, number)   
        previous_type = "number"
      else

The comments here are reversed: table.remove will be getting 'forty', because it was previously pushed onto the stack, and we're adding 'three' to the 'forty'.

Nice implementation though. It seems like a generally useful library to have! And certainly much easier to follow, IMHO, than the SQL thing we saw earlier... ;)

I might reword my comment here too:
      -- If the current word is a number (English or numeric), 
      -- and the previous word was also a number, pop the stack and push the addition. 
      -- Otherwise, push the number.

to something like:
      -- 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.
Amended on Fri 19 Mar 2010 03:04 AM by David Haley
Australia Forum Administrator #55
Thanks David. Amended post to revise your comments.