Something went wrong. Try again.
forked from tailscale/tailscale
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201// Copyright (c) Tailscale Inc & contributors// SPDX-License-Identifier: BSD-3-Clause
// Package unitconv implements string conversion functionality// for unit prefixes such as those from the SI and IEC standards.// It is catered towards printing of values for human consumption.// Consequently it prints of reasonable precision proportional to// the magnitude of the value itself.package unitconv
import ( "bytes" "math" "strconv" "strings" "time" "unicode"
"golang.org/x/exp/constraints")
type numeric interface { constraints.Integer | constraints.Float}
type prefixDivisor struct { prefix string divisor float64}
var prefixDivisorsSI = []prefixDivisor{ {"Y", 1e+24}, {"Z", 1e+21}, {"E", 1e+18}, {"P", 1e+15}, {"T", 1e+12}, {"G", 1e+9}, {"M", 1e+6}, {"k", 1e+3}, {"", 1e0}, {"m", 1e-3}, {"µ", 1e-6}, {"n", 1e-9}, {"p", 1e-12}, {"f", 1e-15}, {"a", 1e-18}, {"z", 1e-21}, {"y", 1e-24},}
var prefixDivisorsIEC = []prefixDivisor{ {"Yi", 1 << 80}, {"Zi", 1 << 70}, {"Ei", 1 << 60}, {"Pi", 1 << 50}, {"Ti", 1 << 40}, {"Gi", 1 << 30}, {"Mi", 1 << 20}, {"Ki", 1 << 10}, {"", 1 << 0},}
// appendFloat is similar to [strconv.AppendFloat] except it limits the output// to a total number of digits rather than a fixed number of digits// after the decimal point.func appendFloat(b []byte, f float64, numDigits int) []byte { i := len(b) b = strconv.AppendFloat(b, f, 'f', numDigits, 64) j := bytes.IndexByte(b[i:], '.') if j >= numDigits { return b[:i+j] } else { return b[:i+numDigits+len(".")] }}
func formatPrefixDivisor(f float64, numDigits int, unitSuffix string, prefixDivisors []prefixDivisor) string { if math.IsNaN(f) || math.IsInf(f, 0) { return strconv.FormatFloat(f, 'f', 0, 64) } var arr [16]byte b := arr[:0] if math.Signbit(f) { f = math.Abs(f) b = append(b, '-') } if f == 0 { return string(append(append(b, '0'), unitSuffix...)) } unit := strings.TrimLeftFunc(unitSuffix, unicode.IsSpace) whitespace := unitSuffix[:len(unitSuffix)-len(unit)] for i, pd := range prefixDivisors { if f >= pd.divisor || i == len(prefixDivisors)-1 { fdiv := f / pd.divisor if float64(uint(fdiv))*pd.divisor == f { b = strconv.AppendUint(b, uint64(fdiv), 10) // exact integer } else { b = appendFloat(b, fdiv, numDigits) } b = append(append(append(b, whitespace...), pd.prefix...), unit...) break } } return string(b)}
// FormatSI formats the number using a metric prefix (e.g. k, M, G, etc.)// combined with the provided unit suffix. Any leading spaces in the unitSuffix// are moved before the metric prefix.//// Example://// FormatSI(123, "P/s") => "123P/s"// FormatSI(12345678, " W") => "12.3 MW"//// Exact precision and width of the output may change in the future.func FormatSI[N numeric](v N, unitSuffix string) string { return formatPrefixDivisor(float64(v), len("999"), unitSuffix, prefixDivisorsSI)}
// FormatIEC formats the number using a binary prefix (e.g. Ki, Mi, Gi, etc.)// combined with the provided unit suffix. Any leading spaces in the unitSuffix// are moved before the metric prefix.//// Example://// FormatIEC(123, "B") => "123B"// FormatIEC(12345678, " B/s") => "11.77 MiB/s"//// Exact precision and width of the output may change in the future.func FormatIEC[N numeric](v N, unitSuffix string) string { return formatPrefixDivisor(float64(v), len("1023"), unitSuffix, prefixDivisorsIEC)}
// FormatDuration formats the duration with precision proportional to// the magnitude of the duration. This differs from the [time.Duration.String]// format in that it additionally supports year ('y') and day ('d') units,// where a year is exactly 365.25 days and a day is exactly 24 hours.//// Examples://// 12ns// 5.12μs// 832μs// 8.14ms// 512ms// 1.3s// 58s// 5m23s// 4h23m// 4d16h// 17y53dfunc FormatDuration(d time.Duration) string { const timeDay = 24 * time.Hour const timeYear = 365*timeDay + timeDay/4 var b []byte if d < 0 { d = -1 * max(d, math.MinInt64+1) // avoid negation overflow b = append(b, '-') } switch { case d < 1e3*time.Nanosecond: return string(append(b, d.Round(time.Nanosecond/1e0).String()...)) case d < 1e1*time.Microsecond: return string(append(b, d.Round(time.Microsecond/1e2).String()...)) case d < 1e2*time.Microsecond: return string(append(b, d.Round(time.Microsecond/1e1).String()...)) case d < 1e3*time.Microsecond: return string(append(b, d.Round(time.Microsecond/1e0).String()...)) case d < 1e1*time.Millisecond: return string(append(b, d.Round(time.Millisecond/1e2).String()...)) case d < 1e2*time.Millisecond: return string(append(b, d.Round(time.Millisecond/1e1).String()...)) case d < 1e3*time.Millisecond: return string(append(b, d.Round(time.Millisecond/1e0).String()...)) case d < 1e1*time.Second: return string(append(b, d.Round(time.Second/1e1).String()...)) case d < time.Minute: return string(append(b, d.Round(time.Second/1e0).String()...)) default: // Invariant: d >= time.Minute at this point units := []struct { unit byte period time.Duration }{{'y', timeYear}, {'d', timeDay}, {'h', time.Hour}, {'m', time.Minute}, {'s', time.Second}} for i, currUnit := range units { if d >= currUnit.period { nextUnit := units[i+1] // always safe since d >= time.Minute if d%currUnit.period%nextUnit.period >= nextUnit.period/2 { d = max(d, d+nextUnit.period/2) // round up in an overflow safe way } b = append(strconv.AppendUint(b, uint64(d/currUnit.period), 10), currUnit.unit) d = d % currUnit.period b = append(strconv.AppendUint(b, uint64(d/nextUnit.period), 10), nextUnit.unit) return string(b) } } panic("unreachable") // since d >= time.Minute }}