Use binary prefixes for size and rate units

Includes adding support for additional size input suffix Mi and MiB, treated equivalent to M.
Extends binary suffix output with letter i, e.g. Ki and Mi.
Centralizes creation of bit/byte unit strings.
This commit is contained in:
albertony
2021-03-02 20:11:57 +01:00
committed by Ivan Andreev
parent 2ec0c8d45f
commit 2925e1384c
62 changed files with 2614 additions and 281 deletions

View File

@ -1,6 +1,6 @@
package fs
// SizeSuffix is parsed by flag with k/M/G suffixes
// SizeSuffix is parsed by flag with K/M/G binary suffixes
import (
"encoding/json"
"fmt"
@ -17,13 +17,21 @@ type SizeSuffix int64
// Common multipliers for SizeSuffix
const (
Byte SizeSuffix = 1 << (iota * 10)
KibiByte
MebiByte
GibiByte
TebiByte
PebiByte
ExbiByte
SizeSuffixBase SizeSuffix = 1 << (iota * 10)
Kibi
Mebi
Gibi
Tebi
Pebi
Exbi
)
const (
// SizeSuffixMax is the largest SizeSuffix multiplier
SizeSuffixMax = Exbi
// SizeSuffixMaxValue is the largest value that can be used to create SizeSuffix
SizeSuffixMaxValue = math.MaxInt64
// SizeSuffixMinValue is the smallest value that can be used to create SizeSuffix
SizeSuffixMinValue = math.MinInt64
)
// Turn SizeSuffix into a string and a suffix
@ -35,24 +43,27 @@ func (x SizeSuffix) string() (string, string) {
return "off", ""
case x == 0:
return "0", ""
case x < 1<<10:
case x < Kibi:
scaled = float64(x)
suffix = ""
case x < 1<<20:
scaled = float64(x) / (1 << 10)
suffix = "k"
case x < 1<<30:
scaled = float64(x) / (1 << 20)
suffix = "M"
case x < 1<<40:
scaled = float64(x) / (1 << 30)
suffix = "G"
case x < 1<<50:
scaled = float64(x) / (1 << 40)
suffix = "T"
case x < Mebi:
scaled = float64(x) / float64(Kibi)
suffix = "Ki"
case x < Gibi:
scaled = float64(x) / float64(Mebi)
suffix = "Mi"
case x < Tebi:
scaled = float64(x) / float64(Gibi)
suffix = "Gi"
case x < Pebi:
scaled = float64(x) / float64(Tebi)
suffix = "Ti"
case x < Exbi:
scaled = float64(x) / float64(Pebi)
suffix = "Pi"
default:
scaled = float64(x) / (1 << 50)
suffix = "P"
scaled = float64(x) / float64(Exbi)
suffix = "Ei"
}
if math.Floor(scaled) == scaled {
return fmt.Sprintf("%.0f", scaled), suffix
@ -67,12 +78,67 @@ func (x SizeSuffix) String() string {
}
// Unit turns SizeSuffix into a string with a unit
func (x SizeSuffix) Unit(unit string) string {
func (x SizeSuffix) unit(unit string) string {
val, suffix := x.string()
if val == "off" {
return val
}
return val + " " + suffix + unit
var suffixUnit string
if suffix != "" && unit != "" {
suffixUnit = suffix + unit
} else {
suffixUnit = suffix + unit
}
return val + " " + suffixUnit
}
// BitUnit turns SizeSuffix into a string with bit unit
func (x SizeSuffix) BitUnit() string {
return x.unit("bit")
}
// BitRateUnit turns SizeSuffix into a string with bit rate unit
func (x SizeSuffix) BitRateUnit() string {
return x.unit("bit/s")
}
// ByteUnit turns SizeSuffix into a string with byte unit
func (x SizeSuffix) ByteUnit() string {
return x.unit("Byte")
}
// ByteRateUnit turns SizeSuffix into a string with byte rate unit
func (x SizeSuffix) ByteRateUnit() string {
return x.unit("Byte/s")
}
// ByteShortUnit turns SizeSuffix into a string with byte unit short form
func (x SizeSuffix) ByteShortUnit() string {
return x.unit("B")
}
// ByteRateShortUnit turns SizeSuffix into a string with byte rate unit short form
func (x SizeSuffix) ByteRateShortUnit() string {
return x.unit("B/s")
}
func (x *SizeSuffix) multiplierFromSymbol(s byte) (found bool, multiplier float64) {
switch s {
case 'k', 'K':
return true, float64(Kibi)
case 'm', 'M':
return true, float64(Mebi)
case 'g', 'G':
return true, float64(Gibi)
case 't', 'T':
return true, float64(Tebi)
case 'p', 'P':
return true, float64(Pebi)
case 'e', 'E':
return true, float64(Exbi)
default:
return false, float64(SizeSuffixBase)
}
}
// Set a SizeSuffix
@ -86,25 +152,42 @@ func (x *SizeSuffix) Set(s string) error {
}
suffix := s[len(s)-1]
suffixLen := 1
multiplierFound := false
var multiplier float64
switch suffix {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.':
suffixLen = 0
multiplier = 1 << 10
multiplier = float64(Kibi)
case 'b', 'B':
multiplier = 1
case 'k', 'K':
multiplier = 1 << 10
case 'm', 'M':
multiplier = 1 << 20
case 'g', 'G':
multiplier = 1 << 30
case 't', 'T':
multiplier = 1 << 40
case 'p', 'P':
multiplier = 1 << 50
if len(s) > 2 && s[len(s)-2] == 'i' {
suffix = s[len(s)-3]
suffixLen = 3
if multiplierFound, multiplier = x.multiplierFromSymbol(suffix); !multiplierFound {
return errors.Errorf("bad suffix %q", suffix)
}
// Could also support SI form MB, and treat it equivalent to MiB, but perhaps better to reserve it for CountSuffix?
//} else if len(s) > 1 {
// suffix = s[len(s)-2]
// if multiplierFound, multiplier = x.multiplierFromSymbol(suffix); multiplierFound {
// suffixLen = 2
// }
//}
} else {
multiplier = float64(SizeSuffixBase)
}
case 'i', 'I':
if len(s) > 1 {
suffix = s[len(s)-2]
suffixLen = 2
multiplierFound, multiplier = x.multiplierFromSymbol(suffix)
}
if !multiplierFound {
return errors.Errorf("bad suffix %q", suffix)
}
default:
return errors.Errorf("bad suffix %q", suffix)
if multiplierFound, multiplier = x.multiplierFromSymbol(suffix); !multiplierFound {
return errors.Errorf("bad suffix %q", suffix)
}
}
s = s[:len(s)-suffixLen]
value, err := strconv.ParseFloat(s, 64)