si.go 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. package humanize
  2. import (
  3. "errors"
  4. "math"
  5. "regexp"
  6. "strconv"
  7. )
  8. var siPrefixTable = map[float64]string{
  9. -24: "y", // yocto
  10. -21: "z", // zepto
  11. -18: "a", // atto
  12. -15: "f", // femto
  13. -12: "p", // pico
  14. -9: "n", // nano
  15. -6: "µ", // micro
  16. -3: "m", // milli
  17. 0: "",
  18. 3: "k", // kilo
  19. 6: "M", // mega
  20. 9: "G", // giga
  21. 12: "T", // tera
  22. 15: "P", // peta
  23. 18: "E", // exa
  24. 21: "Z", // zetta
  25. 24: "Y", // yotta
  26. }
  27. var revSIPrefixTable = revfmap(siPrefixTable)
  28. // revfmap reverses the map and precomputes the power multiplier
  29. func revfmap(in map[float64]string) map[string]float64 {
  30. rv := map[string]float64{}
  31. for k, v := range in {
  32. rv[v] = math.Pow(10, k)
  33. }
  34. return rv
  35. }
  36. var riParseRegex *regexp.Regexp
  37. func init() {
  38. ri := `^([0-9.]+)\s?([`
  39. for _, v := range siPrefixTable {
  40. ri += v
  41. }
  42. ri += `]?)(.*)`
  43. riParseRegex = regexp.MustCompile(ri)
  44. }
  45. // ComputeSI finds the most appropriate SI prefix for the given number
  46. // and returns the prefix along with the value adjusted to be within
  47. // that prefix.
  48. //
  49. // See also: SI, ParseSI.
  50. //
  51. // e.g. ComputeSI(2.2345e-12) -> (2.2345, "p")
  52. func ComputeSI(input float64) (float64, string) {
  53. if input == 0 {
  54. return 0, ""
  55. }
  56. exponent := math.Floor(logn(input, 10))
  57. exponent = math.Floor(exponent/3) * 3
  58. value := input / math.Pow(10, exponent)
  59. // Handle special case where value is exactly 1000.0
  60. // Should return 1M instead of 1000k
  61. if value == 1000.0 {
  62. exponent += 3
  63. value = input / math.Pow(10, exponent)
  64. }
  65. prefix := siPrefixTable[exponent]
  66. return value, prefix
  67. }
  68. // SI returns a string with default formatting.
  69. //
  70. // SI uses Ftoa to format float value, removing trailing zeros.
  71. //
  72. // See also: ComputeSI, ParseSI.
  73. //
  74. // e.g. SI(1000000, B) -> 1MB
  75. // e.g. SI(2.2345e-12, "F") -> 2.2345pF
  76. func SI(input float64, unit string) string {
  77. value, prefix := ComputeSI(input)
  78. return Ftoa(value) + " " + prefix + unit
  79. }
  80. var errInvalid = errors.New("invalid input")
  81. // ParseSI parses an SI string back into the number and unit.
  82. //
  83. // See also: SI, ComputeSI.
  84. //
  85. // e.g. ParseSI(2.2345pF) -> (2.2345e-12, "F", nil)
  86. func ParseSI(input string) (float64, string, error) {
  87. found := riParseRegex.FindStringSubmatch(input)
  88. if len(found) != 4 {
  89. return 0, "", errInvalid
  90. }
  91. mag := revSIPrefixTable[found[2]]
  92. unit := found[3]
  93. base, err := strconv.ParseFloat(found[1], 64)
  94. return base * mag, unit, err
  95. }