github.com/git-lfs/git-lfs@v2.5.2+incompatible/tools/humanize/humanize.go (about) 1 package humanize 2 3 import ( 4 "fmt" 5 "math" 6 "strconv" 7 "strings" 8 "time" 9 "unicode" 10 11 "github.com/git-lfs/git-lfs/errors" 12 ) 13 14 const ( 15 Byte = 1 << (iota * 10) 16 Kibibyte 17 Mebibyte 18 Gibibyte 19 Tebibyte 20 Pebibyte 21 22 Kilobyte = 1000 * Byte 23 Megabyte = 1000 * Kilobyte 24 Gigabyte = 1000 * Megabyte 25 Terabyte = 1000 * Gigabyte 26 Petabyte = 1000 * Terabyte 27 28 // eps is the machine epsilon, or a 64-bit floating point value 29 // reasonably close to zero. 30 eps float64 = 7.0/3 - 4.0/3 - 1.0 31 ) 32 33 var bytesTable = map[string]uint64{ 34 "": Byte, 35 "b": Byte, 36 37 "kib": Kibibyte, 38 "mib": Mebibyte, 39 "gib": Gibibyte, 40 "tib": Tebibyte, 41 "pib": Pebibyte, 42 43 "kb": Kilobyte, 44 "mb": Megabyte, 45 "gb": Gigabyte, 46 "tb": Terabyte, 47 "pb": Petabyte, 48 } 49 50 // ParseBytes parses a given human-readable bytes or ibytes string into a number 51 // of bytes, or an error if the string was unable to be parsed. 52 func ParseBytes(str string) (uint64, error) { 53 var sep int 54 for _, r := range str { 55 if !(unicode.IsDigit(r) || r == '.' || r == ',') { 56 break 57 } 58 59 sep = sep + 1 60 } 61 62 var f float64 63 64 if s := strings.Replace(str[:sep], ",", "", -1); len(s) > 0 { 65 var err error 66 67 f, err = strconv.ParseFloat(s, 64) 68 if err != nil { 69 return 0, err 70 } 71 } 72 73 m, err := ParseByteUnit(str[sep:]) 74 if err != nil { 75 return 0, err 76 } 77 78 f = f * float64(m) 79 if f >= math.MaxUint64 { 80 return 0, errors.New("number of bytes too large") 81 } 82 return uint64(f), nil 83 } 84 85 // ParseByteUnit returns the number of bytes in a given unit of storage, or an 86 // error, if that unit is unrecognized. 87 func ParseByteUnit(str string) (uint64, error) { 88 str = strings.TrimSpace(str) 89 str = strings.ToLower(str) 90 91 if u, ok := bytesTable[str]; ok { 92 return u, nil 93 } 94 return 0, errors.Errorf("unknown unit: %q", str) 95 } 96 97 var sizes = []string{"B", "KB", "MB", "GB", "TB", "PB"} 98 99 // FormatBytes outputs the given number of bytes "s" as a human-readable string, 100 // rounding to the nearest half within .01. 101 func FormatBytes(s uint64) string { 102 var e float64 103 if s == 0 { 104 e = 0 105 } else { 106 e = math.Floor(log(float64(s), 1000)) 107 } 108 109 unit := uint64(math.Pow(1000, e)) 110 suffix := sizes[int(e)] 111 112 return fmt.Sprintf("%s %s", 113 FormatBytesUnit(s, unit), suffix) 114 } 115 116 // FormatBytesUnit outputs the given number of bytes "s" as a quantity of the 117 // given units "u" to the nearest half within .01. 118 func FormatBytesUnit(s, u uint64) string { 119 var rounded float64 120 if s < 10 { 121 rounded = float64(s) 122 } else { 123 rounded = math.Floor(float64(s)/float64(u)*10+.5) / 10 124 } 125 126 format := "%.0f" 127 if rounded < 10 && u > 1 { 128 format = "%.1f" 129 } 130 131 return fmt.Sprintf(format, rounded) 132 } 133 134 // FormatByteRate outputs the given rate of transfer "r" as the quotient of "s" 135 // (the number of bytes transferred) over "d" (the duration of time that those 136 // bytes were transferred in). 137 // 138 // It displays the output as a quantity of a "per-unit-time" unit (i.e., B/s, 139 // MiB/s) in the most representative fashion possible, as above. 140 func FormatByteRate(s uint64, d time.Duration) string { 141 // e is the index of the most representative unit of storage. 142 var e float64 143 144 // f is the floating-point equivalent of "s", so as to avoid more 145 // conversions than necessary. 146 f := float64(s) 147 148 if f != 0 { 149 f = f / d.Seconds() 150 e = math.Floor(log(f, 1000)) 151 if e <= eps { 152 // The result of math.Floor(log(r, 1000)) can be 153 // "close-enough" to zero that it should be effectively 154 // considered zero. 155 e = 0 156 } 157 } 158 159 unit := uint64(math.Pow(1000, e)) 160 suffix := sizes[int(e)] 161 162 return fmt.Sprintf("%s %s/s", 163 FormatBytesUnit(uint64(math.Ceil(f)), unit), suffix) 164 } 165 166 // log takes the log base "b" of "n" (\log_b{n}) 167 func log(n, b float64) float64 { 168 return math.Log(n) / math.Log(b) 169 }