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 | }
|
---|