Source

go-wise / num2eng.go

package main
/* Convert number to English words.

Algorithm from http://mini.net/tcl/591
*/

import (
	"flag"
	"fmt"
	"http"
	"os"
	"regexp"
	"strconv"
	"strings"
	"template"
)

// Tokens from 1000 and up.
var pronounce = []string {
    "vigintillion",
    "novemdecillion",
    "octodecillion",
    "septendecillion",
    "sexdecillion",
    "quindecillion",
    "quattuordecillion",
    "tredecillion",
    "duodecillion",
    "undecillion",
    "decillion",
    "nonillion",
    "octillion",
    "septillion",
    "sextillion",
    "quintillion",
    "quadrillion",
    "trillion",
    "billion",
    "million",
    "thousand",
    "",
}

// Tokens up to 90.
var small = map[string]string {
    "0" : "",
    "1" : "one",
    "2" : "two",
    "3" : "three",
    "4" : "four",
    "5" : "five",
    "6" : "six",
    "7" : "seven",
    "8" : "eight",
    "9" : "nine",
    "10" : "ten",
    "11" : "eleven",
    "12" : "twelve",
    "13" : "thirteen",
    "14" : "fourteen",
    "15" : "fifteen",
    "16" : "sixteen",
    "17" : "seventeen",
    "18" : "eighteen",
    "19" : "nineteen",
    "20" : "twenty",
    "30" : "thirty",
    "40" : "forty",
    "50" : "fifty",
    "60" : "sixty",
    "70" : "seventy",
    "80" : "eighty",
    "90" : "ninety",
}

// Get token <= 90, return '' if not matched.
func getNum(num string) string {
	n, ok := small[num]
	if ok {
		return n
	}
	return ""
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

// Split list to triplets (starting from end). Pad last one with "" if needed.
func triplets(n string) []string {
	result := []string{}
	for i := len(n); i > 0; i -= 3 {
		first := max(i-3, 0)
		chunk := n[first:i]
		for len(chunk) < 3 {
			chunk = fmt.Sprintf("%s ", chunk)
		}
		result = append(result, chunk)
	}

	return result
}

// Normelize number (remove 0"s prefix). Return number and string.
func normNum(num string) (int, string) {
	n, _ := strconv.Atoi(strings.TrimSpace(num))
	return n, fmt.Sprintf("%d", n)
}

// English representaion of a number <= 999.
func small2eng(num string) string {
	n, num := normNum(num)
	hundred, ten := "", ""
	if len(num) == 3 {
		hundred = getNum(string(num[0])) + " hundred"
		num = string(num[1:])
		n, num = normNum(num)
	}

    if n > 20 && n != n/10*10 { // Got ones
		tens := getNum(string(num[0]) + "0")
		ones := getNum(string(num[1]))
        ten = tens + " " + ones
	} else {
        ten = getNum(num)
	}

    if len(hundred)>0 && len(ten)>0 {
        return hundred + " and " + ten
	}

	return hundred + ten
}

// English representation of a number.
func num2eng(num string) (string, os.Error) {
    if len(num)/3 >= len(pronounce) {
		return "", os.NewError("number too big")
	}

    if num == "0" { // Zero is a special case
        return "zero", nil
	}

    // Create reversed number
	parts := []string{} // Result accumolator
	ct := len(pronounce) - 1 // Current index
	for _, chunk := range triplets(num) {
		p := small2eng(chunk)
        if len(p) > 0 {
			parts = append(parts, p + " " + pronounce[ct])
		}
        ct -= 1
	}

	size := len(parts)
	for i := 0; i < size/2; i++ {
		j := size-i-1
		parts[i], parts[j] = parts[j], parts[i]
	}

    return strings.Join(parts, ", "), nil
}

type Reply struct {
	Num string
	English string
	Error string
}

func handler(w http.ResponseWriter, r *http.Request) {
	num := r.FormValue("num")
	reply := Reply{Num: num}

	// Write on return
	defer func() {
		formTemplate.Execute(w, reply)
	}()

	if len(num) == 0 {
		return
	}

	ok, _ := regexp.Match("^[0-9]+$", []byte(num))
	if !ok {
		reply.Error = "Bad number (can be only numbers)"
		return
	}

	english, err := num2eng(num)
	if err != nil {
		reply.Error = err.String()
		return
	}
	reply.English = english
}

var port = flag.Int("port", 8080, "port to run on")

func main() {
	fmt.Printf("Serving on %d\n", *port)
	http.HandleFunc("/", handler)
	http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)
}

var formTemplate = template.Must(template.New("num2eng").Parse(
`
<html>
	<head>
		<style>
			div.error {
				color: red;
			}
			div.english {
				padding-top: 10px;
				font-family: Georgia;
				font-size: 1.5em;
			}
			body {
				width: 80%;
				margin-left: 10%;
				padding: 10px;
				border: 1px solid blue;
			}
			input[name="num"] {
				width: 80%;
			}
		</style>
	</head>
	<body>
	    <h3>Number &rarr; English</h3>
		{{if len .Error }}
		<div class="error">
			{{.Error}}
		</div>
		{{end}}
		<form action="/">
		<input name="num" size="60" value="{{.Num}}" />
		<input type="submit" value="Englishize" />
		<br />
		<div class="english">
			{{.English}}
		</div>
		</form>
	</body>
</html>
`))
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.