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  }