Source

units / fmt.go

Full commit
package units

import "strconv"

// Factor x into lowest prime factor p and everything else e.
func factor(x uint32) (p, e uint32) {
	var i uint32 = 2
	for ; x%i != 0 && i < 20; i++ {
	}
	return i, x / i
}

func unitString(u uint32) string {
	var buf []byte
	var p uint32
	var seenUnit bool
	for u > 1 {
		if seenUnit {
			buf = append(buf, '*')
		}
		seenUnit = true

		p, u = factor(u)
		switch p {
		case meterPart:
			buf = append(buf, 'm')
		case secondPart:
			buf = append(buf, 's')
		case kilogramPart:
			buf = append(buf, 'k', 'g')
		case amperePart:
			buf = append(buf, 'A')
		case kelvinPart:
			buf = append(buf, 'K')
		default:
			return "?"
		}
	}
	return string(buf)
}

func (u unit) String() string {
	numer := unitString(u.numer)
	denom := unitString(u.denom)
	if numer == "?" || denom == "?" {
		return "?unit?"
	}
	if len(denom) > 0 {
		return numer + "/" + denom
	}
	return numer
}

var siUnits = map[unit]string{
	Meter:     "m",
	Second:    "s",
	Kilogram:  "kg",
	Newton:    "N",
	Joule:     "J",
	Watt:      "W",
	Pascal:    "Pa",
	Hertz:     "Hz",
	Kiloliter: "kL",

	Coulomb: "C",
	Ampere:  "A",
	Volt:    "V",
	Ohm:     "\u03a9",
	Farad:   "F",
	Weber:   "Wb",
	Henry:   "H",
	Tesla:   "T",

	Kelvin:   "K",
	Unitless: "",
}

var expPrefix = map[int]string{
	24:  "Y",
	21:  "Z",
	18:  "E",
	15:  "P",
	12:  "T",
	9:   "G",
	6:   "M",
	3:   "k",
	0:   "",
	-3:  "m",
	-6:  "\u03bc",
	-9:  "n",
	-12: "p",
	-15: "f",
	-18: "a",
	-21: "z",
	-24: "y",
}

// Factor x = z * 10^e, where 1 <= |z| < 1000 and e%3==0
func extractExp(x float64) (z float64, e int) {
	if x == 0 {
		return x, 0
	}
	if x != x {
		return x, 0
	}

	var neg bool = x < 0
	if neg {
		x = -x
	}
	var small bool = x < 1
	if small {
		x = 1 / x
	}

	for x >= 1000 && e < 24 {
		x /= 1000
		e += 3
	}

	if small {
		x = 1 / x
		e = -e
		if e != -24 && x < 1 {
			x *= 1000
			e -= 3
		}
	}
	if neg {
		x = -x
	}

	return x, e

}

func (x Value) String() string {
	n := x.num
	e := 0
	symbol, ok := siUnits[x.units]
	if !ok {
		symbol = x.units.String()
	} else {
		if x.units == Kilogram {
			n *= 1000
			symbol = "g"
		}
		if x.units == Kiloliter {
			n *= 1000
			symbol = "L"
		}
		n, e = extractExp(n)
		symbol = expPrefix[e] + symbol
	}
	return strconv.FormatFloat(n, 'g', 6, 64) + symbol
}

var prefixExp map[string]int
var namedUnits map[string]Value

func init() {
	prefixExp = map[string]int{
		"u":      -6,
		"\u00b5": -6,
	}
	for e, p := range expPrefix {
		if e != 0 {
			prefixExp[p] = e
		}
	}

	namedUnits = map[string]Value{
		"g":      Value{.001, Kilogram},
		"l":      Value{.001, Kiloliter},
		"L":      Value{.001, Kiloliter},
		"ohm":    Value{1, Ohm},
		"\u2126": Value{1, Ohm},

		"mi":  Value{1.609344e3, Meter},
		"ft":  Value{0.3048, Meter},
		"in":  Value{0.0254, Meter},
		"lbs": Value{0.45359237, Kilogram},
		"oz":  Value{0.0283495231, Kilogram},
		"gal": Value{0.003785412, Kiloliter},

		"mph": Value{1.609344e3 / 3600, Meter.per(Second)},
		"lbf": Value{4.448222, Newton},
		"hp":  Value{745.7, Watt},

		"h":   Value{3600, Second},
		"min": Value{60, Second},
	}
	for u, n := range siUnits {
		namedUnits[n] = Value{1, u}
	}
	delete(namedUnits, "kg")
	delete(namedUnits, "kL")
}

func parseUnit(s string) (v Value, ok bool) {
	// get the longest valid suffix as the base unit
	i := 0
	for ; i < len(s); i++ {
		suf := s[i:]
		v, ok = namedUnits[suf]
		if ok {
			break
		}
	}
	if !ok || i == 0 {
		return
	}

	prefix := s[:i]
	exp, ok := prefixExp[prefix]
	if !ok {
		return Value{}, false
	}

	var mul float64 = 1000
	if exp < 0 {
		exp = -exp
		mul = .001
	}
	for exp > 0 {
		exp -= 3
		v.num *= mul
	}
	return v, true
}