si.go 2.8 KB

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