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...
Converting numbers from text and vice-versa
Posted by Tiopon on Wed 17 Mar 2010 05:03 AM — 56 posts, 211,481 views.
I think it would be complicated to get right. Why do you want to do it?
Something like this would be fiddly to do:
Something like this would be fiddly to do:
fifty-three million billion eighty million forty-one thousand six hundred and twenty-two.
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?
And the code you used to produce that, Twisol?
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.
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.
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?
The original version had an issue with zero, but I added their quick suggestion for a check right after it starts...
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...
Still too long... I'll split the functions. Third post has the third version.
Here is another SQL function to do the same...
Not exactly sure on that one... or the next.
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.
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;
And its SQL query...
Anyways, I think the first would likely be the easiest to convert. Does that seem right?
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?
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...)
(One of my big issues with SQL code is that it depends so heavily on which DB server you use...)
Nick Gammon said:
And the code you used to produce that, Twisol?
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).
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:
(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,
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
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,622This 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
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".
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
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
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.
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?
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?
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.
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.
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...
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
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
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
Output:
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.
[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.
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").
"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").
This is the problem:
Processing "four hundred thirty-two billion" it is not associating the "four hundred" with the "thirty-two billion".
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".
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
Output:
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.
[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.
Maybe the input "zero" is a special case, after all you don't normally say "twenty-two and zero".
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?
local numbers = {
zero = bc.number (0),
one = bc.number (1),
two = bc.number (2),
three = bc.number (3),
Oh well, if it works, it works.
I amended by post to have that in it.
I amended by post to have that in it.
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
Test results:
[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
And if you put both lots of functions together, and make a little test driver:
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:
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
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.
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
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
/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.
If you want the word "and" in there, and hyphens between "forty" and "two" (ie. forty-two) change convert_up_to_999 to read:
eg.
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
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
p.s. did I mention Pub Crawl? ;)
code copied since we're on a new page:
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
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
oops, add
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.
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.
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?
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.
WillFa said:
oops, add
editted above.
I think; what's the string you tested?
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
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.
Bonus points for converting "the answer to life, the universe, and everything" to 42.
Thank you Twisol. That is a number that is never far from the front of my mind. :P
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...
silly goof.
for i=...
cypher[x]
silly goof.
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?
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?
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?
print(WordtoNum("seven billion nine hundred seventeen million seven hundred sixty one thousand six hundred fifty eight"))
--> 7000000907
WillFa said:
quintillion = bc.number(10^18)
(etc) would solve the issue, right?
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.
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.
Try my test bed:
For example:
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
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.
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.
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:
Otherwise you get:
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
Nick Gammon said:
Try my test bed:
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.
Nick Gammon said:
LPEG result was: 7.1363155460589e+014
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.
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
Nick Gammon said:
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.
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?
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.
I'm hoping that duodecillion is high enough for the use you are planning to put it to in a MUD game.
Something like:
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!!!
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.
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.
This is my final, cleaned up version. It looks a bit neater in places, and handles numbers up to 10^66.
The test bed you can use to confirm it works is this (just put underneath and run it):
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:
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:
(Large numbers need to be quoted, otherwise Lua turns them into scientific notation, like "9.4921277802687e+019", which it won't handle).
Result:
And converting back:
Result:
-- 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
Beautiful and far beyond what I dreamed when I asked. Thanks so much... convert.lua is making me very happy. :)
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.
Thanks David. Amended post to revise your comments.