si.go (2909B)
1 package humanize 2 3 import ( 4 "errors" 5 "math" 6 "regexp" 7 "strconv" 8 ) 9 10 var siPrefixTable = map[float64]string{ 11 -30: "q", // quecto 12 -27: "r", // ronto 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 27: "R", // ronna 31 30: "Q", // quetta 32 } 33 34 var revSIPrefixTable = revfmap(siPrefixTable) 35 36 // revfmap reverses the map and precomputes the power multiplier 37 func revfmap(in map[float64]string) map[string]float64 { 38 rv := map[string]float64{} 39 for k, v := range in { 40 rv[v] = math.Pow(10, k) 41 } 42 return rv 43 } 44 45 var riParseRegex *regexp.Regexp 46 47 func init() { 48 ri := `^([\-0-9.]+)\s?([` 49 for _, v := range siPrefixTable { 50 ri += v 51 } 52 ri += `]?)(.*)` 53 54 riParseRegex = regexp.MustCompile(ri) 55 } 56 57 // ComputeSI finds the most appropriate SI prefix for the given number 58 // and returns the prefix along with the value adjusted to be within 59 // that prefix. 60 // 61 // See also: SI, ParseSI. 62 // 63 // e.g. ComputeSI(2.2345e-12) -> (2.2345, "p") 64 func ComputeSI(input float64) (float64, string) { 65 if input == 0 { 66 return 0, "" 67 } 68 mag := math.Abs(input) 69 exponent := math.Floor(logn(mag, 10)) 70 exponent = math.Floor(exponent/3) * 3 71 72 value := mag / math.Pow(10, exponent) 73 74 // Handle special case where value is exactly 1000.0 75 // Should return 1 M instead of 1000 k 76 if value == 1000.0 { 77 exponent += 3 78 value = mag / math.Pow(10, exponent) 79 } 80 81 value = math.Copysign(value, input) 82 83 prefix := siPrefixTable[exponent] 84 return value, prefix 85 } 86 87 // SI returns a string with default formatting. 88 // 89 // SI uses Ftoa to format float value, removing trailing zeros. 90 // 91 // See also: ComputeSI, ParseSI. 92 // 93 // e.g. SI(1000000, "B") -> 1 MB 94 // e.g. SI(2.2345e-12, "F") -> 2.2345 pF 95 func SI(input float64, unit string) string { 96 value, prefix := ComputeSI(input) 97 return Ftoa(value) + " " + prefix + unit 98 } 99 100 // SIWithDigits works like SI but limits the resulting string to the 101 // given number of decimal places. 102 // 103 // e.g. SIWithDigits(1000000, 0, "B") -> 1 MB 104 // e.g. SIWithDigits(2.2345e-12, 2, "F") -> 2.23 pF 105 func SIWithDigits(input float64, decimals int, unit string) string { 106 value, prefix := ComputeSI(input) 107 return FtoaWithDigits(value, decimals) + " " + prefix + unit 108 } 109 110 var errInvalid = errors.New("invalid input") 111 112 // ParseSI parses an SI string back into the number and unit. 113 // 114 // See also: SI, ComputeSI. 115 // 116 // e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil) 117 func ParseSI(input string) (float64, string, error) { 118 found := riParseRegex.FindStringSubmatch(input) 119 if len(found) != 4 { 120 return 0, "", errInvalid 121 } 122 mag := revSIPrefixTable[found[2]] 123 unit := found[3] 124 125 base, err := strconv.ParseFloat(found[1], 64) 126 return base * mag, unit, err 127 }