gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }