si.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  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. mag := math.Abs(input)
  57. exponent := math.Floor(logn(mag, 10))
  58. exponent = math.Floor(exponent/3) * 3
  59. value := mag / math.Pow(10, exponent)
  60. // Handle special case where value is exactly 1000.0
  61. // Should return 1 M instead of 1000 k
  62. if value == 1000.0 {
  63. exponent += 3
  64. value = mag / math.Pow(10, exponent)
  65. }
  66. value = math.Copysign(value, input)
  67. prefix := siPrefixTable[exponent]
  68. return value, prefix
  69. }
  70. // SI returns a string with default formatting.
  71. //
  72. // SI uses Ftoa to format float value, removing trailing zeros.
  73. //
  74. // See also: ComputeSI, ParseSI.
  75. //
  76. // e.g. SI(1000000, "B") -> 1 MB
  77. // e.g. SI(2.2345e-12, "F") -> 2.2345 pF
  78. func SI(input float64, unit string) string {
  79. value, prefix := ComputeSI(input)
  80. return Ftoa(value) + " " + prefix + unit
  81. }
  82. // SIWithDigits works like SI but limits the resulting string to the
  83. // given number of decimal places.
  84. //
  85. // e.g. SIWithDigits(1000000, 0, "B") -> 1 MB
  86. // e.g. SIWithDigits(2.2345e-12, 2, "F") -> 2.23 pF
  87. func SIWithDigits(input float64, decimals int, unit string) string {
  88. value, prefix := ComputeSI(input)
  89. return FtoaWithDigits(value, decimals) + " " + prefix + unit
  90. }
  91. var errInvalid = errors.New("invalid input")
  92. // ParseSI parses an SI string back into the number and unit.
  93. //
  94. // See also: SI, ComputeSI.
  95. //
  96. // e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil)
  97. func ParseSI(input string) (float64, string, error) {
  98. found := riParseRegex.FindStringSubmatch(input)
  99. if len(found) != 4 {
  100. return 0, "", errInvalid
  101. }
  102. mag := revSIPrefixTable[found[2]]
  103. unit := found[3]
  104. base, err := strconv.ParseFloat(found[1], 64)
  105. return base * mag, unit, err
  106. }