github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/man/si.go (about) 1 package man 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 }