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