[822] | 1 | package humanize
|
---|
| 2 |
|
---|
| 3 | import (
|
---|
| 4 | "errors"
|
---|
| 5 | "math"
|
---|
| 6 | "regexp"
|
---|
| 7 | "strconv"
|
---|
| 8 | )
|
---|
| 9 |
|
---|
| 10 | var siPrefixTable = map[float64]string{
|
---|
| 11 | -24: "y", // yocto
|
---|
| 12 | -21: "z", // zepto
|
---|
| 13 | -18: "a", // atto
|
---|
| 14 | -15: "f", // femto
|
---|
| 15 | -12: "p", // pico
|
---|
| 16 | -9: "n", // nano
|
---|
| 17 | -6: "µ", // micro
|
---|
| 18 | -3: "m", // milli
|
---|
| 19 | 0: "",
|
---|
| 20 | 3: "k", // kilo
|
---|
| 21 | 6: "M", // mega
|
---|
| 22 | 9: "G", // giga
|
---|
| 23 | 12: "T", // tera
|
---|
| 24 | 15: "P", // peta
|
---|
| 25 | 18: "E", // exa
|
---|
| 26 | 21: "Z", // zetta
|
---|
| 27 | 24: "Y", // yotta
|
---|
| 28 | }
|
---|
| 29 |
|
---|
| 30 | var revSIPrefixTable = revfmap(siPrefixTable)
|
---|
| 31 |
|
---|
| 32 | // revfmap reverses the map and precomputes the power multiplier
|
---|
| 33 | func revfmap(in map[float64]string) map[string]float64 {
|
---|
| 34 | rv := map[string]float64{}
|
---|
| 35 | for k, v := range in {
|
---|
| 36 | rv[v] = math.Pow(10, k)
|
---|
| 37 | }
|
---|
| 38 | return rv
|
---|
| 39 | }
|
---|
| 40 |
|
---|
| 41 | var riParseRegex *regexp.Regexp
|
---|
| 42 |
|
---|
| 43 | func init() {
|
---|
| 44 | ri := `^([\-0-9.]+)\s?([`
|
---|
| 45 | for _, v := range siPrefixTable {
|
---|
| 46 | ri += v
|
---|
| 47 | }
|
---|
| 48 | ri += `]?)(.*)`
|
---|
| 49 |
|
---|
| 50 | riParseRegex = regexp.MustCompile(ri)
|
---|
| 51 | }
|
---|
| 52 |
|
---|
| 53 | // ComputeSI finds the most appropriate SI prefix for the given number
|
---|
| 54 | // and returns the prefix along with the value adjusted to be within
|
---|
| 55 | // that prefix.
|
---|
| 56 | //
|
---|
| 57 | // See also: SI, ParseSI.
|
---|
| 58 | //
|
---|
| 59 | // e.g. ComputeSI(2.2345e-12) -> (2.2345, "p")
|
---|
| 60 | func ComputeSI(input float64) (float64, string) {
|
---|
| 61 | if input == 0 {
|
---|
| 62 | return 0, ""
|
---|
| 63 | }
|
---|
| 64 | mag := math.Abs(input)
|
---|
| 65 | exponent := math.Floor(logn(mag, 10))
|
---|
| 66 | exponent = math.Floor(exponent/3) * 3
|
---|
| 67 |
|
---|
| 68 | value := mag / math.Pow(10, exponent)
|
---|
| 69 |
|
---|
| 70 | // Handle special case where value is exactly 1000.0
|
---|
| 71 | // Should return 1 M instead of 1000 k
|
---|
| 72 | if value == 1000.0 {
|
---|
| 73 | exponent += 3
|
---|
| 74 | value = mag / math.Pow(10, exponent)
|
---|
| 75 | }
|
---|
| 76 |
|
---|
| 77 | value = math.Copysign(value, input)
|
---|
| 78 |
|
---|
| 79 | prefix := siPrefixTable[exponent]
|
---|
| 80 | return value, prefix
|
---|
| 81 | }
|
---|
| 82 |
|
---|
| 83 | // SI returns a string with default formatting.
|
---|
| 84 | //
|
---|
| 85 | // SI uses Ftoa to format float value, removing trailing zeros.
|
---|
| 86 | //
|
---|
| 87 | // See also: ComputeSI, ParseSI.
|
---|
| 88 | //
|
---|
| 89 | // e.g. SI(1000000, "B") -> 1 MB
|
---|
| 90 | // e.g. SI(2.2345e-12, "F") -> 2.2345 pF
|
---|
| 91 | func SI(input float64, unit string) string {
|
---|
| 92 | value, prefix := ComputeSI(input)
|
---|
| 93 | return Ftoa(value) + " " + prefix + unit
|
---|
| 94 | }
|
---|
| 95 |
|
---|
| 96 | // SIWithDigits works like SI but limits the resulting string to the
|
---|
| 97 | // given number of decimal places.
|
---|
| 98 | //
|
---|
| 99 | // e.g. SIWithDigits(1000000, 0, "B") -> 1 MB
|
---|
| 100 | // e.g. SIWithDigits(2.2345e-12, 2, "F") -> 2.23 pF
|
---|
| 101 | func SIWithDigits(input float64, decimals int, unit string) string {
|
---|
| 102 | value, prefix := ComputeSI(input)
|
---|
| 103 | return FtoaWithDigits(value, decimals) + " " + prefix + unit
|
---|
| 104 | }
|
---|
| 105 |
|
---|
| 106 | var errInvalid = errors.New("invalid input")
|
---|
| 107 |
|
---|
| 108 | // ParseSI parses an SI string back into the number and unit.
|
---|
| 109 | //
|
---|
| 110 | // See also: SI, ComputeSI.
|
---|
| 111 | //
|
---|
| 112 | // e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil)
|
---|
| 113 | func ParseSI(input string) (float64, string, error) {
|
---|
| 114 | found := riParseRegex.FindStringSubmatch(input)
|
---|
| 115 | if len(found) != 4 {
|
---|
| 116 | return 0, "", errInvalid
|
---|
| 117 | }
|
---|
| 118 | mag := revSIPrefixTable[found[2]]
|
---|
| 119 | unit := found[3]
|
---|
| 120 |
|
---|
| 121 | base, err := strconv.ParseFloat(found[1], 64)
|
---|
| 122 | return base * mag, unit, err
|
---|
| 123 | }
|
---|