-
Notifications
You must be signed in to change notification settings - Fork 1
/
prefix.go
165 lines (148 loc) · 6.13 KB
/
prefix.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package humanize
import (
"fmt"
"math"
"math/big"
"regexp"
"sort"
"strconv"
"strings"
)
// Prefixing functions.
// Single prefix definition.
type prefixDef struct {
value *big.Float
approxValue float64 // For faster comparisons. Is it needed though?
short string
long string
}
var siPrefixes = []prefixDef{
{bigPow(10, 24), math.Pow10(24), "Y", "yotta"},
{bigPow(10, 21), math.Pow10(21), "Z", "zetta"},
{bigPow(10, 18), math.Pow10(18), "E", "exa"},
{bigPow(10, 15), math.Pow10(15), "P", "peta"},
{bigPow(10, 12), math.Pow10(12), "T", "tera"},
{bigPow(10, 9), math.Pow10(9), "G", "giga"},
{bigPow(10, 6), math.Pow10(6), "M", "mega"},
{bigPow(10, 3), math.Pow10(3), "k", "kilo"},
{bigPow(10, 2), math.Pow10(2), "h", "hecto"},
{bigPow(10, 1), 10, "da", "deca"},
{bigPow(10, -1), math.Pow10(-1), "d", "deci"},
{bigPow(10, -2), math.Pow10(-2), "c", "centi"},
{bigPow(10, -3), math.Pow10(-3), "m", "milli"},
{bigPow(10, -6), math.Pow10(-6), "µ", "micro"},
{bigPow(10, -9), math.Pow10(-9), "n", "nano"},
{bigPow(10, -12), math.Pow10(-12), "p", "pico"},
{bigPow(10, -15), math.Pow10(-15), "f", "femto"},
{bigPow(10, -18), math.Pow10(-18), "a", "atto"},
{bigPow(10, -21), math.Pow10(-21), "z", "zepto"},
{bigPow(10, -24), math.Pow10(-24), "y", "yocto"},
}
var bitPrefixes = []prefixDef{
{bigPow(2, 80), math.Pow(2, 80), "Yi", "yobi"},
{bigPow(2, 70), math.Pow(2, 70), "Zi", "zebi"},
{bigPow(2, 60), math.Pow(2, 60), "Ei", "exbi"},
{bigPow(2, 50), math.Pow(2, 50), "Pi", "pebi"},
{bigPow(2, 40), math.Pow(2, 40), "Ti", "tebi"},
{bigPow(2, 30), math.Pow(2, 30), "Gi", "gibi"},
{bigPow(2, 20), math.Pow(2, 20), "Mi", "mebi"},
{bigPow(2, 10), math.Pow(2, 10), "Ki", "kibi"},
}
// preparePrefixes will build a regular expression to match all possible prefix inputs.
func (humanizer *Humanizer) preparePrefixes() {
// Save all prefixes into one slice - for convenience.
humanizer.allPrefixes = append(humanizer.allPrefixes, siPrefixes...)
humanizer.allPrefixes = append(humanizer.allPrefixes, bitPrefixes...)
// List of all prefixes as strings.
prefixes := make([]string, 0, len(humanizer.allPrefixes))
// Append prefixes.
for _, prefix := range humanizer.allPrefixes {
// Use this loop to also translate the long versions.
prefix.long = humanizer.provider.prefixes[prefix.short]
prefixes = append(prefixes, prefix.long)
prefixes = append(prefixes, prefix.short)
}
// Regexp will match: number, optional coma or dot, optional second number, optional space, optional suffix.
humanizer.prefixInputRe = regexp.MustCompile(
`([0-9]+)[.,]?([0-9]*?) ?(` + strings.Join(prefixes, "|") + `)?$`)
}
// Performs the actual prefixing.
func (humanizer *Humanizer) prefix(value float64, decimals int, threshold int64, short bool, bit bool) string {
prefixes := siPrefixes
if bit {
prefixes = bitPrefixes
}
if threshold < 10 {
threshold = 10
}
// If value falls within ignored range then just format it.
if value <= float64(threshold) && value >= 10.0/float64(threshold) {
return trimZeroes(strconv.FormatFloat(value, 'f', decimals, 64))
}
// Find most appropriate prefix.
i := sort.Search(len(prefixes), func(i int) bool {
return prefixes[i].approxValue < value
})
if i == len(prefixes) { // prefixDef not found.
return trimZeroes(strconv.FormatFloat(value, 'f', decimals, 64))
}
// For prefixing the approximate value should be enough.
convertedValue := trimZeroes(
strconv.FormatFloat(value/prefixes[i].approxValue, 'f', decimals, 64))
if short {
return convertedValue + prefixes[i].short
}
return convertedValue + " " + prefixes[i].long
}
// BitPrefixFast is a convenience wrapper over BitPrefix.
// Precision is 2 decimal place. Will not prefix values smaller than 1024 and will append only the short prefix.
func (humanizer *Humanizer) BitPrefixFast(value float64) string {
return humanizer.BitPrefix(value, 2, 1024, true)
}
// SiPrefixFast is a convenience function for easy prefixing with a SI prefix.
// Precision is 1 decimal place. Will not prefix values in range 0.01 - 1000 and will append only the short prefix.
func (humanizer *Humanizer) SiPrefixFast(value float64) string {
return humanizer.SiPrefix(value, 1, 1000, true)
}
// SiPrefix appends a SI prefix to the value and converts it accordingly.
// Arguments:
// value - value to be converted.
// decimals - decimal precision for the converted value.
// threshold - upper bound of the value range to be ignored. Lower bound is 1/threshold.
// short - whether to use short or long prefix.
func (humanizer *Humanizer) SiPrefix(value float64, decimals int, threshold int64, short bool) string {
return humanizer.prefix(value, decimals, threshold, short, false)
}
// BitPrefix appends a bit prefix to the value and converts it accordingly.
// Arguments:
// value - value to be converted (should be in bytes).
// decimals - decimal precision for the converted value.
// threshold - upper bound of the value range to be ignored. Lower bound is 1/threshold.
// short - whether to use short or long prefix.
func (humanizer *Humanizer) BitPrefix(value float64, decimals int, threshold int64, short bool) string {
return humanizer.prefix(value, decimals, threshold, short, true)
}
// ParsePrefix will return a number as parsed from input string.
func (humanizer *Humanizer) ParsePrefix(input string) (*big.Float, error) {
matched := humanizer.prefixInputRe.FindStringSubmatch(strings.TrimSpace(input))
// 0 - full match, 1 - number, 2 - decimal, 3 - suffix
if len(matched) != 4 {
return new(big.Float), fmt.Errorf("Cannot parse '%s'.", input)
}
// Parse first two groups as a float.
// This can only fail if the regexp is wrong and allows non numbers.
number, _ := new(big.Float).SetString(matched[1] + "." + matched[2])
// No suffix, no multiplication.
if matched[3] == "" {
return number, nil
}
// Get the multiplier for the prefix.
for _, prefix := range humanizer.allPrefixes {
if prefix.short == matched[3] || prefix.long == matched[3] {
result := new(big.Float).Mul(number, prefix.value)
return result, nil
}
}
// No prefix was found. This should never happen as the regexp covers all units.
return new(big.Float), fmt.Errorf("Can't match prefix for '%s'.", matched[3])
}