Here is the custom function to spell number to words using LAMBDA(). I would appreciate any improvement of the function or simplification if possible. Also suggest best practice for the function.

=LAMBDA(num,LET(x,ABS(num),wd,TEXT(INT(x),"000000000000000"),dec,ROUND(x-INT(x),2)*100,c,CHOOSECOLS,
digits,{"One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten","Eleven","Twelve","Thirteen","Fourteen","Fifteen","Sixteen","Seventeen","Eighteen","Nineteen"},
Tenths,{"Ten","Twenty","Thirty","Fourty","Fifty","Sixty","Seventy","Eighty","Ninety","Hundred"},
tr,--MID(wd,1,3),bln,--MID(wd,4,3),mln,--MID(wd,7,3),Hazar,--MID(wd,10,3),Shotok,--MID(wd,13,1),Dosok,--MID(wd,14,2),
SPELL,LAMBDA(val,z,IFERROR(IF(val<20,c(digits,val)&" "&z&" ",c(Tenths,LEFT(val,1))&IFERROR(" "&c(digits,RIGHT(val,1)),"")&" "&z&" "),"")),
SPELL3,LAMBDA(vl,abb,IF(vl>99,SPELL(--LEFT(vl,1),"Hundred")&IF(--RIGHT(vl,2)=0,abb,SPELL(--RIGHT(vl,2), abb)),SPELL(vl,abb))),
dlr,SPELL3(tr,"Trillion")&SPELL3(bln,"Billion")&SPELL3(mln,"Million")&SPELL3(Hazar,"Thousand")&SPELL(Shotok,"Hundred")&SPELL(Dosok,""),
Cnt,IF(dec>1,SPELL(dec,"Cents Only."),SPELL(dec,"Cent Only.")),
TRIM(IF(AND(--wd>0,dec>0),dlr&" Dollar"&IFS(x>1,"s")&" And "&Cnt,IF(dlr="",Cnt,dlr&" Dollar"&IFS(x>1,"s")&" Only.")))))(B2)

Screenshot of SpellNumber function.

12 Replies 12

Is this usefull? =SUBSTITUTE(SUBSTITUTE(TRANSLATE(BAHTTEXT(B2)),"baht","dollar"),"satang","cent(s)")

@P.b This do not give correct output all the time. So, this is not reliable. I tested it previously.

@Harun24hr Can you give any specific example cases of when that fails?

For values less than one dollar, it returns,eg:

0.02  --> Dollar and Two Cent Only

Also:

100000 --> One Hundred Dollar Only.

@Chronocidal =SUBSTITUTE(SUBSTITUTE(TRANSLATE(BAHTTEXT(100000)),"baht","dollar"),"satang","cent(s)") give output as 100,000 dollar.

@Ron Rosenfeld Thank you for pointing the bug. I have corrected those two bug. Improved formula-

=LAMBDA(num,LET(x,ABS(num),wd,TEXT(INT(x),"000000000000000"),dec,ROUND(x-INT(x),2)*100,c,CHOOSECOLS,
digits,{"One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten","Eleven","Twelve","Thirteen","Fourteen","Fifteen","Sixteen","Seventeen","Eighteen","Nineteen"},
Tenths,{"Ten","Twenty","Thirty","Fourty","Fifty","Sixty","Seventy","Eighty","Ninety","Hundred"},
tr,--MID(wd,1,3),bln,--MID(wd,4,3),mln,--MID(wd,7,3),Hazar,--MID(wd,10,3),Shotok,--MID(wd,13,1),Dosok,--MID(wd,14,2),
SPELL,LAMBDA(val,z,IFERROR(IF(val<20,c(digits,val)&" "&z&" ",c(Tenths,LEFT(val,1))&IFERROR(c(digits,RIGHT(val,1)),"")&" "&z&" "),"")),
SPELL3,LAMBDA(vl,abb,IF(vl>99,SPELL(--LEFT(vl,1),"Hundred")&IF(--RIGHT(vl,2)<=0,abb,SPELL(--RIGHT(vl,2), abb)),SPELL(vl,abb))),
Dollor,SPELL3(tr,"Trillion")&SPELL3(bln,"Billion")&SPELL3(mln,"Million")&SPELL3(Hazar,"Thousand")&SPELL(Shotok,"Hundred")&SPELL(Dosok,""),
Cent,SPELL(dec,"Cent Only."),TRIM(IF(AND(Dollor<>"",Cent<>""),Dollor&" Dollar And "&Cent,IF(Dollor="",Cent,Dollor&" Dollar Only.")))))(B2)

This is only slightly shorter (1,031 characters vs. yours at 1,035), but removed lambda functions and used Named functions instead so every group of three digits could be processed recursively instead of sequentially. It also moves all the word lists to a single place into a calling shell routine to simplify the function call. Finally, the logic around dollars and cents was moved to a third, inline formula which fixes the problems you have with pluralizing dollars and cents.

Starting from the top down, the formula to print Dollars and Cents is:

Let(D,Int(A1),C,mod(round(A1*100),100),If(D>0,INTTOWORDS(D) & " Dollar"
& If(D>1,"s",""),"") & If(C>0,If(D>0," and ","") & INTTOWORDS(C) & " Cent"
& If(C>1,"s",""),""))

Here A1 is the currency amount to be turned into words. Notice it calls on a named function, INTTOWORDS, to actually print the numbers in the correct context of the words, 'Dollar'(s), ' and ', 'Cent'(s). The INTTOWORDS function is a simple calling shell whose named function definition is this:

Named Function: INTTOWORDS, parameter1: nbr

=NBRWORDS(nbr,{"";" thousand";" million";" billion";" trillion"},
{"";"";"twenty";"thirty";"forty";"fifty";"sixty";"seventy";"eighty";"ninety"},
{"";"one";"two";"three";"four";"five"; "six"; "seven"; "eight";"nine"; "ten";
 "eleven"; "twelve"; "thirteen"; "fourteen"; "fifteen"; "sixteen"; "seventeen";
 "eighteen"; "nineteen"})

Notice that this only calls another named function, NBRWORDS, which then includes all the words we need to construct each group of three digits. The first array, which I call the Suffix, is initialized in this call, but the top row gets popped off with each successive call as it processes the "thousands" group and then the "millions" group, etc. successively. The NBRWORDS logic basically processes a group of 3 digits, adding the correct suffix, and calls itself for the next group of 3 digits, until it runs out.

Named Function: NBRWORDS parameter1:nbr parameter2:Suffix(array) parameter3:tenswords(array) parameter4: oneswords(array)

=Let(hundreds,mod(Int(Nbr/100),10), tens,mod(Int(Nbr/10),10), ones,mod(Int(Nbr),10)+If(tens=1,10,0),
NextNbr,Int(Nbr/1000), If(NextNbr>0,NBRWORDS(NextNbr
 ,MakeArray(Rows(Suffix)-1,1,LAMBDA(Row,Col,Index(Suffix,Row+1,Col))), tenswords, oneswords),"")
& If(mod(Int(Nbr),1000)>0,
 If(hundreds>0, If(NextNbr>0," ","") & Index(oneswords,hundreds+1) & " hundred","")
& If(tens>1,If(hundreds+NextNbr>0," ","") & index(tenswords,tens+1),"")
& If(ones>0,If(hundreds+NextNbr+tens>0," ","") & index(oneswords,ones+1),"")
& Index(Suffix,1),""))

The MakeArray is the function that 'pops' the first row off the Suffix array in the recursive call to NBRWORDS. There's still quite a bit of IF() conditional text concatenation especially around getting the right spaces between parts which keeps it pretty verbose.

Here are some of the actual results:

Google Sheets screenshot with formula results

Notice B2 gets the pluralization right. B1 shows a large number with zeros in different places. Need to see other examples?

Edit

Hey @Harun24hr: Did some research into making recursive calls to Lambda functions and came up with the following:

You can use a Let() function to give a Lambda function a name, but you cannot refer to it directly in the body of the lambda because that name has not been has not been defined until the body of the Lambda is fully completed and you will get a #NAME? error.

INCORRECT--> =Let(RecFctrl, Lambda(N, N*If(N>1,RecFctrl(N-1),1)), RecFctrl(9))

What you can do instead is add one more parameter to the Lambda call and pass down the Lambda function itself inside the Lambda call. So the correct code to get factorials computed is:

CORRECT--> =Let(RecFctrl, Lambda(self, N, N*If(N>1,self(self,N-1),1)), RecFctrl(RecFctrl,9))

This added another arbitrarily-named parameter, self, and then used it inside the body of the Lambda itself as a function call and also passed it down to the next level call as self(self,N-1). Then, in the body of the Let() it was referred to by its defined name, RecFctrl, but once again the function was passed down as RecFctrl(RecFctrl,9).

Kind of a long-winded explanation, but this meant it was possible to put the recursive Lambda function into an inline formula, combining the two Named Functions above, with only a few minor changes:

=Let(D,Int(A1),C,mod(round(A1*100),100),
  WdsFromN,Lambda(this,N, Suffix, tenswords, oneswords,
   Let(hundreds,mod(Int(N/100),10), tens,mod(Int(N/10),10),
   ones,mod(Int(N),10)+If(tens=1,10,0), NextN,Int(N/1000),
     TextJoin(" ",TRUE, If(NextN>0, this(this,NextN,
     MakeArray(Rows(Suffix)-1,1,LAMBDA(Row,Col,Index(Suffix,Row+1,Col))), 
     tenswords, oneswords),""),
     If(hundreds>0,Index(oneswords,hundreds+1) & " hundred",""),
     index(tenswords,tens+1), index(oneswords,ones+1),
     If(mod(Int(N),1000)>0,Index(Suffix,1),"")))),
  NToWds,LAMBDA(nbr,WdsFromN(WdsFromN,nbr,
     "";"thousand";"million";"billion";"trillion"},
     {"";"";"twenty";"thirty";"forty";"fifty";"sixty";"seventy";"eighty";"ninety"},
     {"";"one";"two";"three";"four";"five"; "six"; "seven"; "eight";"nine"; "ten";
      "eleven"; "twelve"; "thirteen"; "fourteen"; "fifteen"; "sixteen";
      "seventeen"; "eighteen"; "nineteen"})),
 If(D>0,NToWds(D) & " Dollar" & If(D>1,"s",""),"")
   & If(C>0,If(D>0," and ","") & NToWds(C) & " Cent" & If(C>1,"s",""),""))

This is pretty much the same code as above but combined into one unwieldy formula, which is still shorter (993 characters) than yours (1035). You can shorten it still further by replacing the long MAKEARRAY() function with a DROP(SUFFIX,1) instead. I did not do that because everything in this formula also runs in Google Sheets, but the DROP function is not defined in Sheets (where I am testing this formula). Now, here are some tests of the combined formula:

enter image description here

@Chris Maurer Thanks for response and suggestions. I personally want to keep whole things into one function. We can split the function using different name manager but in development phase I want to keep it in one. Can you merge your functions into one then we can test it by applying different numbers.

Not sure if you would consider this to be an improvement, but you could eliminate variables tr, bln, mln, Hazar, Shotok and Dosok and generate their results programmatically within the dlr/Dollor variable. For example:

dlr,CONCAT(MAP(--MID(wd,{1,4,7,10,13,14},{3,3,3,3,1,2}),{"Trillion","Billion","Million","Thousand","Hundred",""},IF({1,1,1,1,0,0},SPELL3,SPELL),LAMBDA(v,a,fn,fn(v,a)))),

Having said that, I also took a crack at this task using some alternative methods:

=LAMBDA(number,
    LET(
        num, ABS(number),
        int, INT(num),
        dec, ROUND(num - int, 2) * 100,
        oneλ, LAMBDA(x, CHOOSE(x + 1, "", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten",
            "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen")),
        tenλ, LAMBDA(x, CHOOSE(QUOTIENT(x, 10), "Ten", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety")),
        strλ, LAMBDA(x, IF(x < 20, oneλ(x), TEXTJOIN("-", 1, tenλ(x), oneλ(MOD(x, 10))))),
        dolλ, LAMBDA(x, FILTER(HSTACK(IF(x, MAP(x, strλ) & {" Hundred",""}, ""), {"Trillion";"Billion";"Million";"Thousand";""}), BYROW(x, OR))),
        TEXTJOIN(" ", 1,
            SWITCH(SIGN(number), -1, "Negative", ""),
            IF(int, dolλ(--MID(TEXT(int, REPT(0, 15)), TAKE(SEQUENCE(5, 3),, 2), {1,2})), "Zero"),
            SWITCH(int, 1, "Dollar", "Dollars"),
            "and",
            IF(dec, strλ(dec), "Zero"),
            SWITCH(dec, 1, "Cent", "Cents")
        )
    )
)

Note: static arrays {" Hundred",""} and {1,2} are both horizontal vectors, whereas {"Trillion";"Billion";"Million";"Thousand";""} is a vertical vector. Adjust the row and column separators if needed, as per your system settings.

The output is slightly different than yours, returning "Zero Dollars" and "Zero Cents" when applicable, although I'm sure it could be modified to meet your specific needs. Kind regards.

EDIT: as requested...

@Harun24hr said: "I will tweak your formula to eliminate zero dollar and zero cent when there is no value. Or you can also do that for me."

=LAMBDA(number,
    LET(
        num, ABS(number),
        int, INT(num),
        dec, ROUND(num - int, 2) * 100,
        oneλ, LAMBDA(x, CHOOSE(x + 1, "", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten",
            "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen")),
        tenλ, LAMBDA(x, CHOOSE(QUOTIENT(x, 10), "Ten", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety")),
        strλ, LAMBDA(x, IF(x < 20, oneλ(x), TEXTJOIN("-",, tenλ(x), oneλ(MOD(x, 10))))),
        dolλ, LAMBDA(x, TEXTJOIN(" ",, FILTER(HSTACK(IF(x, MAP(x, strλ) & {" Hundred",""}, ""), {"Trillion";"Billion";"Million";"Thousand";""}), BYROW(x, OR)))),
        TEXTJOIN(" ",,
            SWITCH(SIGN(number), -1, "Negative", ""),
            IF(int, dolλ(--MID(TEXT(int, REPT(0, 15)), TAKE(SEQUENCE(5, 3),, 2), {1,2})) & SWITCH(int, 1, " Dollar", " Dollars"), ""),
            IF(AND(int, dec), "and", ""),
            IF(dec, strλ(dec) & SWITCH(dec, 1, " Cent", " Cents"), ""),
            "Only."
        )
    )
)

@Djc Nice! This is dlr,CONCAT(MAP(--MID(wd,{1,4,7,10,13,14},{3,3,3,3,1,2}),{"Trillion","Billion","Million","Thousand","Hundred",""},IF({1,1,1,1,0,0},SPELL3,SPELL),LAMBDA(v,a,fn,fn(v,a)))), definitely a improvement. Thanks for suggestions. I will tweak your formula to eliminate zero dollar and zero cent when there is no value. Or you can also do that for me.

@Chris Maurer Yes, I do and improved few parts of the formula. I will test your formula also. Another good formula from @Djc.

@Harun24hr I've edited my original response as requested (see above). Also, I reviewed your formula again and modified portions of it as follows:

=LAMBDA(number,
    LET(
        num, ABS(number),
        int, INT(num),
        dec, ROUND(num - int, 2) * 100,
        txt, TEXT(int, "000000000000000"),
        one, {"One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten","Eleven","Twelve","Thirteen","Fourteen","Fifteen","Sixteen","Seventeen","Eighteen","Nineteen"},
        ten, {"Ten","Twenty","Thirty","Forty","Fifty","Sixty","Seventy","Eighty","Ninety","Hundred"},
        cc, CHOOSECOLS,
        fn, LAMBDA(me,val,abr,[tri],
            IF(
                tri,
                IF(val > 99, me(me, --LEFT(val, 1), "Hundred ") & LAMBDA(vl, IF(vl,  me(me, vl, abr), abr & " "))(--RIGHT(val, 2)), me(me, val, abr)),
                IFERROR(IF(val < 20, cc(one, val), cc(ten, LEFT(val, 1)) & IFERROR(" " & cc(one, RIGHT(val, 1)), "")) & " " & abr & " ", "")
            )
        ),
        SPELL, LAMBDA(v,a,[t], fn(fn,v,a,t)),
        doll, CONCAT(MAP(--MID(txt, {1,4,7,10,13,14}, {3,3,3,3,1,2}), {"Trillion","Billion","Million","Thousand","Hundred",""}, {1,1,1,1,0,0}, SPELL)),
        cent, SPELL(dec, "Cent Only."),
        TRIM(IF(AND(int, dec), doll & " Dollar And " & cent, IF(dec, cent, doll & " Dollar Only.")))
    )
)

The most notable change I made was to merge SPELL and SPELL3 into a single recursive function with an optional [tri] argument to toggle between the two routines. I also updated the doll variable accordingly and renamed a few other variables. Other than that, I tried to stay as true as possible to your original function. Hopefully it works as expected.

Your Reply

By clicking “Post Your Reply”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.