Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Code Golf: Number to Words

Ok, I think it's time for my own implementation in Windows BATCH script (should work on Windows 2000 or later).

Here is the code:

@echo off

set zero_to_nineteen=Zero One Two Three Four Five Six Seven Eight Nine Ten Eleven Twelve Thirteen Fourteen Fifteen Sixteen Seventeen Eighteen Nineteen
set twenty_to_ninety=ignore ignore Twenty Thirty Forty Fifty Sixty Seventy Eighty Ninety
set big_numbers=ignore Thousand Million Billion Trillion Quadrillion Quintillion Sextillion Septillion Octillion Nonillion Decillion Undecillion Duodecillion Tredecillion Quattuordecillion Quindecillion Sexdecillion Septendecillion Octodecillion Novemdecillion Vigintillion
rem             10^0   10^3     10^6    10^9    10^12    10^15       10^18       10^21      10^24      10^27     10^30     10^33     10^36       10^39        10^42        10^45             10^48         10^51        10^54           10^57         10^60          10^63

call :parse_numbers %*

exit /B 0

:parse_numbers
    :parse_numbers_loop
        if "$%~1" == "$" goto parse_numbers_end
        call :parse_number %~1
        echo %~1 -^> %parse_number_result%
        shift
        goto parse_numbers_loop
    :parse_numbers_end
    exit /B 0

:parse_number
    call :get_sign %~1
    set number_sign=%get_sign_result%
    call :remove_groups %get_sign_result_number%
    call :trim_leading_zeros %remove_groups_result%
    set number=%trim_leading_zeros_result%
    if "$%number%" == "$0" (
        set parse_number_result=Zero
        exit /B 0
    )
    set counter=0
    set parse_number_result=
    :parse_number_loop
        set last_three=%number:~-3%
        set number=%number:~0,-3%
        call :parse_three %last_three%
        call :get_from %counter% %big_numbers%
        if "$%get_from_result%" == "$" (
            set parse_number_result=* ERR: the number is too big! Even wikipedia doesn't know how it's called!
            exit /B 0
        )
        if not "$%parse_three_result%" == "$Zero" (
            if %counter% == 0 (
                set parse_number_result=%parse_three_result%
            ) else (
                if not "$%parse_number_result%" == "$" (
                    set parse_number_result=%parse_three_result% %get_from_result% %parse_number_result%
                ) else (
                    set parse_number_result=%parse_three_result% %get_from_result%
                )
            )
        )
        set /A counter+=1
        if not "$%number%" == "$" goto parse_number_loop
    if "$%parse_number_result%" == "$" (
        set parse_number_result=Zero
        exit /B 0
    ) else if not "$%number_sign%" == "$" (
        set parse_number_result=%number_sign% %parse_number_result%
    )
    exit /B 0

:parse_three
    call :trim_leading_zeros %~1
    set three=%trim_leading_zeros_result%
    set /A three=%three% %% 1000
    set /A two=%three% %% 100
    call :parse_two %two%
    set parse_three_result=
    set /A digit=%three% / 100
    if not "$%digit%" == "$0" (
        call :get_from %digit% %zero_to_nineteen%
    )
    if not "$%digit%" == "$0" (
        if not "$%get_from_result%" == "$Zero" (
            set parse_three_result=%get_from_result% Hundred
        )
    )
    if "$%parse_two_result%" == "$Zero" (
        if "$%parse_three_result%" == "$" (
            set parse_three_result=Zero
        )
    ) else (
        if "$%parse_three_result%" == "$" (
            set parse_three_result=%parse_two_result%
        ) else (
            set parse_three_result=%parse_three_result% %parse_two_result%
        )
    )
    exit /B 0

:parse_two
    call :trim_leading_zeros %~1
    set two=%trim_leading_zeros_result%
    set /A two=%two% %% 100
    call :get_from %two% %zero_to_nineteen%
    if not "$%get_from_result%" == "$" (
        set parse_two_result=%get_from_result%
        goto parse_two_20_end
    )
    set /A digit=%two% %% 10
    call :get_from %digit% %zero_to_nineteen%
    set parse_two_result=%get_from_result%
    set /A digit=%two% / 10
    call :get_from %digit% %twenty_to_ninety%
    if not "$%parse_two_result%" == "$Zero" (
        set parse_two_result=%get_from_result% %parse_two_result%
    ) else (
        set parse_two_result=%get_from_result%
    )
    goto parse_two_20_end
    :parse_two_20_end
    exit /B 0

:get_from
    call :trim_leading_zeros %~1
    set idx=%trim_leading_zeros_result%
    set /A idx=0+%~1
    shift
    :get_from_loop
        if "$%idx%" == "$0" goto get_from_loop_end
        set /A idx-=1
        shift
        goto get_from_loop
    :get_from_loop_end
    set get_from_result=%~1
    exit /B 0

:trim_leading_zeros
    set str=%~1
    set trim_leading_zeros_result=
    :trim_leading_zeros_loop
        if not "$%str:~0,1%" == "$0" (
            set trim_leading_zeros_result=%trim_leading_zeros_result%%str%
            exit /B 0
        )
        set str=%str:~1%
        if not "$%str%" == "$" goto trim_leading_zeros_loop
    if "$%trim_leading_zeros_result%" == "$" set trim_leading_zeros_result=0
    exit /B 0

:get_sign
    set str=%~1
    set sign=%str:~0,1%
    set get_sign_result=
    if "$%sign%" == "$-" (
        set get_sign_result=Minus
        set get_sign_result_number=%str:~1%
    ) else if "$%sign%" == "$+" (
        set get_sign_result_number=%str:~1%
    ) else (
        set get_sign_result_number=%str%
    )
    exit /B 0

:remove_groups
    set str=%~1
    set remove_groups_result=%str:'=%
    exit /B 0

This is the test script I used:

@echo off
rem 10^x:x= 66  63  60  57  54  51  48  45  42  39  36  33  30  27  24  21  18  15  12   9   6   3   0
call number                                                                                          0
call number                                                                                          2
call number                                                                                        -17
call number                                                                                         30
call number                                                                                         48
call number                                                                                       -256
call number                                                                                        500
call number                                                                                        874
call number                                                                                      1'024
call number                                                                                    -17'001
call number                                                                                    999'999
call number                                                                                  1'048'576
call number                                                                         -1'000'001'000'000
call number                                                                    912'345'014'587'957'003
call number                                                       -999'912'345'014'587'124'337'999'999
call number                                        111'222'333'444'555'666'777'888'999'000'000'000'001
call number                               -912'345'014'587'912'345'014'587'124'912'345'014'587'124'337
call number    999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999
call number  1'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000
rem 10^x:x= 66  63  60  57  54  51  48  45  42  39  36  33  30  27  24  21  18  15  12   9   6   3   0

And this is the output I got from my test script:

0 -> Zero
2 -> Two
-17 -> Minus Seventeen
30 -> Thirty
48 -> Forty Eight
-256 -> Minus Two Hundred Fifty Six
500 -> Five Hundred
874 -> Eight Hundred Seventy Four
1'024 -> One Thousand Twenty Four
-17'001 -> Minus Seventeen Thousand One
999'999 -> Nine Hundred Ninety Nine Thousand Nine Hundred Ninety Nine
1'048'576 -> One Million Forty Eight Thousand Five Hundred Seventy Six
-1'000'001'000'000 -> Minus One Trillion One Million
912'345'014'587'957'003 -> Nine Hundred Twelve Quadrillion Three Hundred Forty Five Trillion Fourteen Billion Five Hundred Eighty Seven Million Nine Hundred Fifty Seven Thousand Three
-999'912'345'014'587'124'337'999'999 -> Minus Nine Hundred Ninety Nine Septillion Nine Hundred Twelve Sextillion Three Hundred Forty Five Quintillion Fourteen Quadrillion Five Hundred Eighty Seven Trillion One Hundred Twenty Four Billion Three Hundred Thirty Seven Million Nine Hundred Ninety Nine Thousand Nine Hundred Ninety Nine
111'222'333'444'555'666'777'888'999'000'000'000'001 -> One Hundred Eleven Undecillion Two Hundred Twenty Two Decillion Three Hundred Thirty Three Nonillion Four Hundred Forty Four Octillion Five Hundred Fifty Five Septillion Six Hundred Sixty Six Sextillion Seven Hundred Seventy Seven Quintillion Eight Hundred Eighty Eight Quadrillion Nine Hundred Ninety Nine Trillion One
-912'345'014'587'912'345'014'587'124'912'345'014'587'124'337 -> Minus Nine Hundred Twelve Tredecillion Three Hundred Forty Five Duodecillion Fourteen Undecillion Five Hundred Eighty Seven Decillion Nine Hundred Twelve Nonillion Three Hundred Forty Five Octillion Fourteen Septillion Five Hundred Eighty Seven Sextillion One Hundred Twenty Four Quintillion Nine Hundred Twelve Quadrillion Three Hundred Forty Five Trillion Fourteen Billion Five Hundred Eighty Seven Million One Hundred Twenty Four Thousand Three Hundred Thirty Seven
999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999 -> Nine Hundred Ninety Nine Vigintillion Nine Hundred Ninety Nine Novemdecillion Nine Hundred Ninety Nine Octodecillion Nine Hundred Ninety Nine Septendecillion Nine Hundred Ninety Nine Sexdecillion Nine Hundred Ninety Nine Quindecillion Nine Hundred Ninety Nine Quattuordecillion Nine Hundred Ninety Nine Tredecillion Nine Hundred Ninety Nine Duodecillion Nine Hundred Ninety Nine Undecillion Nine Hundred Ninety Nine Decillion Nine Hundred Ninety Nine Nonillion Nine Hundred Ninety Nine Octillion Nine Hundred Ninety Nine Septillion Nine Hundred Ninety Nine Sextillion Nine Hundred Ninety Nine Quintillion Nine Hundred Ninety Nine Quadrillion Nine Hundred Ninety Nine Trillion Nine Hundred Ninety Nine Billion Nine Hundred Ninety Nine Million Nine Hundred Ninety Nine Thousand Nine Hundred Ninety Nine
1'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000 -> * ERR: the number is too big! Even wikipedia doesn't know how it's called!

If I could find some more names of large numbers, the script would support even bigger numbers. Currently, though, the script will work with all numbers from -(10^66-1) to (10^66-1).

I have to mention, that I had a lot of fun solving this in BATCH. :)


C# - 30 lines incl. method declaration and { }s:

Takes into account all the previously aforementioned commas, ands and hyphens. I've only included up to octillion because decimal.MaxValue is only in the octillions. For bigger integers you would need to add corresponding items to the thou[] array and perhaps pass the number in as a string, modifying the line to extract the block by using the last 3 chars instead of using modulo as I have here.

    static string wordify(decimal v)
    {
        if (v == 0) return "zero";
        var units = " one two three four five six seven eight nine".Split();
        var teens = " eleven twelve thir# four# fif# six# seven# eigh# nine#".Replace("#", "teen").Split();
        var tens = " ten twenty thirty forty fifty sixty seventy eighty ninety".Split();
        var thou = " thousand m# b# tr# quadr# quint# sext# sept# oct#".Replace("#", "illion").Split();
        var g = (v < 0) ? "minus " : "";
        var w = "";
        var p = 0;
        v = Math.Abs(v);
        while (v > 0)
        {
            int b = (int)(v % 1000);
            if (b > 0)
            {
                var h = (b / 100);
                var t = (b - h * 100) / 10;
                var u = (b - h * 100 - t * 10);
                var s = ((h > 0) ? units[h] + " hundred" + ((t > 0 | u > 0) ? " and " : "") : "")
                      + ((t > 0) ? (t == 1 && u > 0) ? teens[u] : tens[t] + ((u > 0) ? "-" : "") : "")
                      + ((t != 1) ? units[u] : "");
                s = (((v > 1000) && (h == 0) && (p == 0)) ? " and " : (v > 1000) ? ", " : "") + s;
                w = s + " " + thou[p] + w;
            }
            v = v / 1000;
            p++;
        }
        return g + w;
    }

Called using:

static void Main(string[] args)
{
  Console.WriteLine(wordify(decimal.MaxValue));
}

Output:

seventy-nine octillion, two hundred and twenty-eight septillion, one hundred and sixty-two sextillion, five hundred and fourteen quintillion, two hundred and sixty-four quadrillion, three hundred and thirty-seven trillion, five hundred and ninety-three billion, five hundred and forty-three million, nine hundred and fifty thousand, three hundred and thirty-five


In A86 assember - assembles to a .COM executable:

dd 0ba02c6bfh, 0b8bd10c1h, 0e808b512h, 0ea870100h, 08700e9e8h, 010273eah
dd 0e0e8c2h, 06b51872h, 0c000ebe8h, 0b3c02e8h, 03368067dh, 0b2e901h
dd 0baaa5004h, 0fd8110c1h, 0cd7c1630h, 0bf3031bbh, 0a0571000h, 0ec880080h
dd 0c581c589h, 023c0081h, 0e7f087ch, 0823e38h, 027b00875h, 0e901d068h
dd 0b6400080h, 04f6f603h, 080d08a1ch, 0b60f80c4h, 07f06c7f4h, 088303000h
dd 0ac00813eh, 087ef828h, 0b00056e8h, 051e81dh, 0d83850adh, 0e7f157ch
dd 0a74fc38h, 0262ce088h, 0e901a368h, 01d2c003bh, 0580036e8h, 0b7efc38h
dd 0774d838h, 0f828e088h, 0800026e8h, 0127e1dfah, 0afd448ah, 0440afe44h
dd 074f838ffh, 0e8c28a05h, 0cafe000fh, 0ab7cee39h, 05a2405c6h, 021cd09b4h
dd 05e856c3h, 020b05e00h, 0c5bec3aah, 074c00a02h, 03c80460ah, 0fefa755bh
dd 046f675c8h, 0745b3cach, 0f8ebaae8h, 0eec1d689h, 08a3c8a03h, 07e180cah
dd 0cfd2c1feh, 0ebe8c342h, 0fed8d0ffh, 0c3f775cdh, 01e581e8fh, 0303c5ea8h
dd 0df6f652ah, 078bde03ch, 05e027500h, 01ec1603ch, 07d40793dh, 0603c8080h
dd 09f6f2838h, 040f17a3dh, 080f17a22h, 0403d7264h, 0793cdee1h, 0140740f1h
dd 01e2f7d32h, 02f488948h, 0a7c43b05h, 0a257af9bh, 0be297b6ch, 04609e30ah
dd 0b8f902abh, 07c21e13eh, 09a077d9eh, 054f82ab5h, 0fabe2af3h, 08a6534cdh
dd 0d32b4c97h, 035c7c8ceh, 082bcc833h, 0f87f154fh, 0650ff7eah, 02f143fdfh
dd 0a1fd687fh, 0c3e687fdh, 0c6d50fe0h, 075f13574h, 0898c335bh, 0e748ce85h
dd 08769676fh, 0ad2cedd3h, 0928c77c7h, 077e2d18eh, 01a77e8f6h
db 0bah, 01bh

That's a 454 byte executable.

Here's the (slightly smaller) code. Since A86 is an 8086 only assembler, I've had to hand code the 32bit extensions:

    mov di,strings
    mov dx,tree_data * 8 + 1
    mov bp,code_data * 8
l1:
    mov ch,8
    call extract_bits
    xchg dx,bp
    call extract_bit
    xchg dx,bp
    jnc l2
    add dx,ax
l2:
    call extract_bit
    jc l3
    mov ch,6
    call extract_bits
    shr al,2
    cmp al,11
    push l27
    jl get_string
l25:
    add al,48+32
    stosb
l27:
    mov dx,tree_data * 8 + 1
l3:
    cmp bp,end_data * 8
    jl l1

convert:
    mov bx,'01'
    mov di,01000h
    push di

    mov al,[80h]
    mov ah,ch
    mov bp,ax
    add bp,81h
    cmp al,2
    jl zero
    jg l90
    cmp byte ptr [82h],bh
    jne l90
zero:   
    mov al,39
    push done

get_string:
    mov si,strings-1
    or al,al
    je l36
l35:
    inc si
    cmp byte ptr [si],';'+32
    jne l35
    dec al
    jnz l35
l36:
    inc si
l37:
    lodsb
    cmp al,';'+32
    je ret
    stosb
    jmp l37


l90:
    inc ax
    mov dh,3
    div dh
    add al,28
    mov dl,al
    add ah,80h
    db 0fh, 0b6h, 0f4h ; movzx si,ah
    mov word ptr [80h],'00'

l95:    
    lodsb

    sub al,bh
    jle l100
    call get_string2
    mov al,29
    call get_string2

l100:
    lodsw
    push ax
    cmp al,bl
    jl l150
    jg l140
    cmp ah,bh
    je l140

    mov al,ah
    sub al,'0'-10
    push l150

get_string2:
    push si
    call get_string
    pop si
    mov al,' '
    stosb
    ret

l140:
    sub al,'0'-19
    call get_string2

l150:
    pop ax
    cmp ah,bh
    jle l200
    cmp al,bl
    je l200
    mov al,ah
    sub al,bh
    call get_string2

l200:
    cmp dl,29
    jle l300

    mov al,[si-3]
    or al,[si-2]
    or al,[si-1]
    cmp al,bh
    je l300

    mov al,dl
    call get_string2

l300:
    dec dl
    cmp si,bp
    jl l95

done:   
    mov byte ptr [di],'$'
    pop dx
    mov ah,9
    int 21h 
    int 20h

l41:
    rcr al,1
    dec ch
    jz ret

extract_bits:
    push l41
extract_bit:
    mov si,dx
    shr si,3
    mov bh,[si]
    mov cl,dl
    and cl,7
    inc cl
    ror bh,cl
    inc dx
    ret

tree_data:
    dw 01e8fh, 01e58h, 05ea8h, 0303ch, 0652ah, 0df6fh, 0e03ch, 078bdh
    dw 07500h, 05e02h, 0603ch, 01ec1h, 0793dh, 07d40h, 08080h, 0603ch
    dw 02838h, 09f6fh, 07a3dh, 040f1h, 07a22h, 080f1h, 07264h, 0403dh
    dw 0dee1h, 0793ch, 040f1h, 01407h, 07d32h, 01e2fh, 08948h
    db 048h
code_data:
    dw 052fh, 0c43bh, 09ba7h, 057afh, 06ca2h, 0297bh, 0abeh, 09e3h
    dw 0ab46h, 0f902h, 03eb8h, 021e1h, 09e7ch, 077dh, 0b59ah, 0f82ah
    dw 0f354h, 0be2ah, 0cdfah, 06534h, 0978ah, 02b4ch, 0ced3h, 0c7c8h
    dw 03335h, 0bcc8h, 04f82h, 07f15h, 0eaf8h, 0ff7h, 0df65h, 0143fh
    dw 07f2fh, 0fd68h, 0fda1h, 0e687h, 0e0c3h, 0d50fh, 074c6h, 0f135h
    dw 05b75h, 08c33h, 08589h, 048ceh, 06fe7h, 06967h, 0d387h, 02cedh
    dw 0c7adh, 08c77h, 08e92h, 0e2d1h, 0f677h, 077e8h, 0ba1ah
    db 01bh
end_data:

strings:

The text is stored using Huffman encoding. The command line is passed as a string so converting it is simple - split the string into groups of three and parse each group (hundreds, tens and units) following each with the current multiplier (millions, thousands, etc).


Lisp, using only standard functions:

(format nil "~r" 1234) ==> "one thousand two hundred thirty-four"

Bonus:

(format nil "~@r" 1234)  ==> "MCCXXXIV"