Files
lancet/formatter/byte.go

207 lines
4.7 KiB
Go

package formatter
import (
"fmt"
"math"
"strconv"
"strings"
"unicode"
"github.com/duke-git/lancet/v2/mathutil"
"github.com/duke-git/lancet/v2/strutil"
)
//
// code logic come from:
// https://github.com/docker/go-units/blob/master/size.go
// http://en.wikipedia.org/wiki/Binary_prefix
const (
// Decimal
unitB = 1
unitKB = 1000
unitMB = 1000 * unitKB
unitGB = 1000 * unitMB
unitTB = 1000 * unitGB
unitPB = 1000 * unitTB
unitEB = 1000 * unitPB
// Binary
unitBiB = 1
unitKiB = 1024
unitMiB = 1024 * unitKiB
unitGiB = 1024 * unitMiB
unitTiB = 1024 * unitGiB
unitPiB = 1024 * unitTiB
unitEiB = 1024 * unitPiB
)
// type byteUnitMap map[byte]int64
var (
decimalByteMap = map[string]uint64{
"b": unitB,
"kb": unitKB,
"mb": unitMB,
"gb": unitGB,
"tb": unitTB,
"pb": unitPB,
"eb": unitEB,
// Without suffix
"": unitB,
"k": unitKB,
"m": unitMB,
"g": unitGB,
"t": unitTB,
"p": unitPB,
"e": unitEB,
}
binaryByteMap = map[string]uint64{
"bi": unitBiB,
"kib": unitKiB,
"mib": unitMiB,
"gib": unitGiB,
"tib": unitTiB,
"pib": unitPiB,
"eib": unitEiB,
// Without suffix
"": unitBiB,
"ki": unitKiB,
"mi": unitMiB,
"gi": unitGiB,
"ti": unitTiB,
"pi": unitPiB,
"ei": unitEiB,
}
)
var (
decimalByteUnits = []string{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
binaryByteUnits = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
)
// DecimalBytes returns a human readable byte size under decimal standard (base 1000)
// The precision parameter specifies the number of digits after the decimal point, which defaults to 4.
// Play: https://go.dev/play/p/FPXs1suwRcs
func DecimalBytes(size float64, precision ...int) string {
pointPosition := 4
if len(precision) > 0 {
pointPosition = precision[0]
}
size, unit := calculateByteSize(size, 1000.0, decimalByteUnits)
return roundToToString(size, pointPosition) + unit
}
// BinaryBytes returns a human-readable byte size under binary standard (base 1024)
// The precision parameter specifies the number of digits after the decimal point, which defaults to 4.
// Play: https://go.dev/play/p/G9oHHMCAZxP
func BinaryBytes(size float64, precision ...int) string {
pointPosition := 4
if len(precision) > 0 {
pointPosition = precision[0]
}
size, unit := calculateByteSize(size, 1024.0, binaryByteUnits)
return roundToToString(size, pointPosition) + unit
}
func calculateByteSize(size float64, base float64, byteUnits []string) (float64, string) {
i := 0
unitsLimit := len(byteUnits) - 1
for size >= base && i < unitsLimit {
size = size / base
i++
}
return size, byteUnits[i]
}
func roundToToString(x float64, max ...int) string {
pointPosition := 4
if len(max) > 0 {
pointPosition = max[0]
}
result := mathutil.RoundToString(x, pointPosition)
// 删除小数位结尾的0
decimal := strings.TrimRight(strutil.After(result, "."), "0")
if decimal == "" || pointPosition == 0 {
// 没有小数位直接返回整数
return strutil.Before(result, ".")
}
// 小数位大于想要设置的位数,按需要截断
if len(decimal) > pointPosition {
return strutil.Before(result, ".") + "." + decimal[:pointPosition]
}
// 小数位小于等于想要的位数,直接拼接返回
return strutil.Before(result, ".") + "." + decimal
}
// ParseDecimalBytes return the human readable bytes size string into the amount it represents(base 1000).
// ParseDecimalBytes("42 MB") -> 42000000, nil
// Play: https://go.dev/play/p/Am98ybWjvjj
func ParseDecimalBytes(size string) (uint64, error) {
return parseBytes(size, "decimal")
}
// ParseBinaryBytes return the human readable bytes size string into the amount it represents(base 1024).
// ParseBinaryBytes("42 mib") -> 44040192, nil
// Play: https://go.dev/play/p/69v1tTT62x8
func ParseBinaryBytes(size string) (uint64, error) {
return parseBytes(size, "binary")
}
// see https://github.com/dustin/go-humanize/blob/master/bytes.go
func parseBytes(s string, kind string) (uint64, error) {
lastDigit := 0
hasComma := false
for _, r := range s {
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
break
}
if r == ',' {
hasComma = true
}
lastDigit++
}
num := s[:lastDigit]
if hasComma {
num = strings.Replace(num, ",", "", -1)
}
f, err := strconv.ParseFloat(num, 64)
if err != nil {
return 0, err
}
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
if kind == "decimal" {
if m, ok := decimalByteMap[extra]; ok {
f *= float64(m)
if f >= math.MaxUint64 {
return 0, fmt.Errorf("too large: %v", s)
}
return uint64(f), nil
}
} else {
if m, ok := binaryByteMap[extra]; ok {
f *= float64(m)
if f >= math.MaxUint64 {
return 0, fmt.Errorf("too large: %v", s)
}
return uint64(f), nil
}
}
return 0, fmt.Errorf("unhandled size name: %v", extra)
}