diff --git a/bytes.go b/bytes.go index 0b498f4..67b6839 100644 --- a/bytes.go +++ b/bytes.go @@ -31,6 +31,11 @@ const ( EByte = PByte * 1000 ) +var ( + nameSizes = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"} + iNameSizes = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"} +) + var bytesSizeTable = map[string]uint64{ "b": Byte, "kib": KiByte, @@ -65,19 +70,47 @@ func logn(n, b float64) float64 { return math.Log(n) / math.Log(b) } -func humanateBytes(s uint64, base float64, sizes []string) string { +func precisionSprint(val float64, precision int) string { + _, frac := math.Modf(val) + if frac == 0 { + return fmt.Sprintf("%.0f", val) + } + f := fmt.Sprintf("%%.%df", precision) + res := fmt.Sprintf(f, val) + return strings.TrimRight(res, "0") +} + +func simpleSprint(val float64) string { + f := "%.0f" + if val < 10 { + f = "%.1f" + } + return fmt.Sprintf(f, val) +} + +func humanateBytes(s uint64, base float64, sizes []string, precision int, specRoundFunc func(float64) float64) string { if s < 10 { return fmt.Sprintf("%d B", s) } e := math.Floor(logn(float64(s), base)) suffix := sizes[int(e)] - val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10 - f := "%.0f %s" - if val < 10 { - f = "%.1f %s" + + var addlNumber float64 + if specRoundFunc == nil { + specRoundFunc = math.Floor + addlNumber = 0.5 + } + + val := specRoundFunc(float64(s)/math.Pow(base, e)*math.Pow(10, float64(precision))+addlNumber) / math.Pow(10, float64(precision)) + + var roundVal string + if precision > 1 { + roundVal = precisionSprint(val, precision) + } else { + roundVal = simpleSprint(val) } - return fmt.Sprintf(f, val, suffix) + return fmt.Sprintf("%s %s", roundVal, suffix) } // Bytes produces a human readable representation of an SI size. @@ -86,8 +119,21 @@ func humanateBytes(s uint64, base float64, sizes []string) string { // // Bytes(82854982) -> 83 MB func Bytes(s uint64) string { - sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"} - return humanateBytes(s, 1000, sizes) + return humanateBytes(s, 1000, nameSizes, 1, nil) +} + +// BytesCustomFloor allow to set precision and to get less or equal value with rounding +// +// BytesCustomFloor(92160871366656, 2) -> 92.16 TB +func BytesCustomFloor(s uint64, precision int) string { + return humanateBytes(s, 1000, nameSizes, precision, math.Floor) +} + +// BytesCustomCeil allow to set precision and to get more or equal value with rounding +// +// BytesCustomCeil(92160871366656, 2) -> 92.17 TB +func BytesCustomCeil(s uint64, precision int) string { + return humanateBytes(s, 1000, nameSizes, precision, math.Ceil) } // IBytes produces a human readable representation of an IEC size. @@ -96,8 +142,22 @@ func Bytes(s uint64) string { // // IBytes(82854982) -> 79 MiB func IBytes(s uint64) string { - sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"} - return humanateBytes(s, 1024, sizes) + return humanateBytes(s, 1024, iNameSizes, 1, nil) +} + +// IBytesCustomFloor allow to set precision and to get less or equal value with rounding +// +// IBytesCustomFloor(92160871366656, 2) -> 83.81 TiB +// IBytesCustomFloor(92160871366656, 3) -> 83.8198242187 +func IBytesCustomFloor(s uint64, precision int) string { + return humanateBytes(s, 1024, iNameSizes, precision, math.Floor) +} + +// IBytesCustomCeil allow to set precision and to get more or equal value with rounding +// +// IBytesCustomCeil(92160871366656, 2) -> 83.82 TiB +func IBytesCustomCeil(s uint64, precision int) string { + return humanateBytes(s, 1024, iNameSizes, precision, math.Ceil) } // ParseBytes parses a string representation of bytes into the number diff --git a/bytes_test.go b/bytes_test.go index 0bb811c..fc9059e 100644 --- a/bytes_test.go +++ b/bytes_test.go @@ -100,6 +100,7 @@ func TestBytes(t *testing.T) { {"bytes(1PB - 1T)", Bytes(EByte - PByte), "999 PB"}, {"bytes(1EB)", Bytes(EByte), "1.0 EB"}, + {"bytes(92160871366656)", Bytes(92160871366656), "92 TB"}, // Overflows. // {"bytes(1EB - 1P)", Bytes((KByte*EByte)-PByte), "1023EB"}, @@ -129,7 +130,138 @@ func TestBytes(t *testing.T) { {"bytes(5.5GiB)", IBytes(5.5 * GiByte), "5.5 GiB"}, - {"bytes(5.5GB)", Bytes(5.5 * GByte), "5.5 GB"}, + {"bytes(92160871366656)", IBytes(92160871366656), "84 TiB"}, + }.validate(t) +} + +func TestBytesCustomFloor(t *testing.T) { + testList{ + {"Floor : bytes(0) with precision(2) ", BytesCustomFloor(0, 2), "0 B"}, + {"Floor : bytes(1) with precision(2)", BytesCustomFloor(1, 2), "1 B"}, + {"Floor : bytes(803) with precision(2)", BytesCustomFloor(803, 2), "803 B"}, + {"Floor : bytes(999) with precision(2)", BytesCustomFloor(999, 2), "999 B"}, + + {"Floor : bytes(1) with precision(2)", BytesCustomFloor(1, 2), "1 B"}, + {"Floor : bytes(803) with precision(2)", BytesCustomFloor(803, 2), "803 B"}, + {"Floor : bytes(999) with precision(2)", BytesCustomFloor(999, 2), "999 B"}, + + {"Floor : bytes(1024) with precision(2)", BytesCustomFloor(1024, 2), "1.02 kB"}, + {"Floor : bytes(9999) with precision(2)", BytesCustomFloor(9999, 2), "9.99 kB"}, + {"Floor : bytes(1MB - 1) with precision(2)", BytesCustomFloor(MByte-Byte, 2), "999.99 kB"}, + + {"Floor : bytes(1MB) with precision(2)", BytesCustomFloor(1024*1024, 2), "1.04 MB"}, + {"Floor : bytes(1GB - 1K) with precision(2)", BytesCustomFloor(GByte-KByte, 2), "999.99 MB"}, + + {"Floor : bytes(1GB) with precision(2)", BytesCustomFloor(GByte, 2), "1 GB"}, + {"Floor : bytes(1TB - 1M) with precision(2)", BytesCustomFloor(TByte-MByte, 2), "999.99 GB"}, + {"Floor : bytes(10MB) with precision(2)", BytesCustomFloor(9999*1000, 2), "9.99 MB"}, + + {"Floor : bytes(1TB) with precision(2)", BytesCustomFloor(TByte, 2), "1 TB"}, + {"Floor : bytes(1PB - 1T) with precision(2)", BytesCustomFloor(PByte-TByte, 2), "999 TB"}, + + {"Floor : bytes(1PB) with precision(2)", BytesCustomFloor(PByte, 2), "1 PB"}, + {"Floor : bytes(1PB - 1T) with precision(2)", BytesCustomFloor(EByte-PByte, 2), "999 PB"}, + + {"Floor : bytes(1EB) with precision(2)", BytesCustomFloor(EByte, 2), "1 EB"}, + + {"Floor : bytes(92160871366656) with precision(2)", BytesCustomFloor(92160871366656, 2), "92.16 TB"}, + {"Floor : bytes(92160871366656) with precision(10)", BytesCustomFloor(92160871366656, 10), "92.1608713666 TB"}, + {"Floor : bytes(92160871366656) with precision(3)", BytesCustomFloor(92160866656, 3), "92.16 GB"}, + {"Floor : bytes(92160871366656) with precision(2)", BytesCustomFloor(92160866656, 2), "92.16 GB"}, + {"Floor : bytes(92160871366656) with precision(0)", BytesCustomFloor(102160871366656, 0), "102 TB"}, + {"Floor : bytes(92160871366656) with precision(20)", BytesCustomFloor(102160871366656, 20), "102.16087136665599643948 TB"}, + + {"Floor : bytes(0) with precision(2)", IBytesCustomFloor(0, 2), "0 B"}, + {"Floor : bytes(1) with precision(2)", IBytesCustomFloor(1, 2), "1 B"}, + {"Floor : bytes(803) with precision(2)", IBytesCustomFloor(803, 2), "803 B"}, + {"Floor : bytes(1023) with precision(2)", IBytesCustomFloor(1023, 2), "1023 B"}, + + {"Floor : bytes(1024) with precision(2)", IBytesCustomFloor(1024, 2), "1 KiB"}, + {"Floor : bytes(1MB - 1) with precision(2)", IBytesCustomFloor(MiByte-IByte, 2), "1023.99 KiB"}, + + {"Floor : bytes(1MB) with precision(2)", IBytesCustomFloor(1024*1024, 2), "1 MiB"}, + {"Floor : bytes(1GB - 1K) with precision(2)", IBytesCustomFloor(GiByte-KiByte, 2), "1023.99 MiB"}, + + {"Floor : bytes(1GB) with precision(2)", IBytesCustomFloor(GiByte, 2), "1 GiB"}, + {"Floor : bytes(1TB - 1M) with precision(2)", IBytesCustomFloor(TiByte-MiByte, 2), "1023.99 GiB"}, + + {"Floor : bytes(1TB) with precision(2)", IBytesCustomFloor(TiByte, 2), "1 TiB"}, + {"Floor : bytes(1PB - 1T) with precision(2)", IBytesCustomFloor(PiByte-TiByte, 2), "1023 TiB"}, + + {"Floor : bytes(1PB) with precision(2)", IBytesCustomFloor(PiByte, 2), "1 PiB"}, + {"Floor : bytes(1PB - 1T) with precision(2)", IBytesCustomFloor(EiByte-PiByte, 2), "1023 PiB"}, + + {"Floor : bytes(1EiB) with precision(1)", IBytesCustomFloor(EiByte, 2), "1 EiB"}, + + {"Floor : bytes(5.5GiB) with precision(3)", IBytesCustomFloor(5.5*GiByte, 3), "5.5 GiB"}, + + {"Floor : bytes(92160871366656) with precision(2)", IBytesCustomFloor(92160871366656, 2), "83.81 TiB"}, + {"Floor : bytes(92160871366656) with precision(10)", IBytesCustomFloor(92160871366656, 10), "83.8198242187 TiB"}, + {"Floor : bytes(92160871366656) with precision(3)", IBytesCustomFloor(92160866656, 3), "85.831 GiB"}, + {"Floor : bytes(92160871366656) with precision(2)", IBytesCustomFloor(92160866656, 2), "85.83 GiB"}, + {"Floor : bytes(92160871366656) with precision(0)", IBytesCustomFloor(102160871366656, 0), "92 TiB"}, + {"Floor : bytes(92160871366656) with precision(20)", IBytesCustomFloor(102160871366656, 20), "92.91477123647928237915 TiB"}, + }.validate(t) +} +func TestBytesCustomCeil(t *testing.T) { + testList{ + {"Ceil : bytes(0) with precision(2) ", BytesCustomCeil(0, 2), "0 B"}, + {"Ceil : bytes(1) with precision(2)", BytesCustomCeil(1, 2), "1 B"}, + {"Ceil : bytes(803) with precision(2)", BytesCustomCeil(803, 2), "803 B"}, + {"Ceil : bytes(999) with precision(2)", BytesCustomCeil(999, 2), "999 B"}, + + {"Ceil : bytes(1) with precision(2)", BytesCustomCeil(1, 2), "1 B"}, + {"Ceil : bytes(803) with precision(2)", BytesCustomCeil(803, 2), "803 B"}, + {"Ceil : bytes(999) with precision(2)", BytesCustomCeil(999, 2), "999 B"}, + + {"Ceil : bytes(1024) with precision(2)", BytesCustomCeil(1024, 2), "1.03 kB"}, + {"Ceil : bytes(9999) with precision(2)", BytesCustomCeil(9999, 2), "10 kB"}, + {"Ceil : bytes(1MB - 1) with precision(2)", BytesCustomCeil(MByte-Byte, 2), "1000 kB"}, + + {"Ceil : bytes(1MB) with precision(2)", BytesCustomCeil(1024*1024, 2), "1.05 MB"}, + {"Ceil : bytes(1GB - 1K) with precision(2)", BytesCustomCeil(GByte-KByte, 2), "1000 MB"}, + + {"Ceil : bytes(1GB) with precision(2)", BytesCustomCeil(GByte, 2), "1 GB"}, + {"Ceil : bytes(1TB - 1M) with precision(2)", BytesCustomCeil(TByte-MByte, 2), "1000 GB"}, + {"Ceil : bytes(10MB) with precision(2)", BytesCustomCeil(9999*1000, 2), "10 MB"}, + {"Ceil : bytes(1TB) with precision(2)", BytesCustomCeil(TByte, 2), "1 TB"}, + {"Ceil : bytes(1PB - 1T) with precision(2)", BytesCustomCeil(PByte-TByte, 2), "999 TB"}, + {"Ceil : bytes(1PB) with precision(2)", BytesCustomCeil(PByte, 2), "1 PB"}, + {"Ceil : bytes(1PB - 1T) with precision(2)", BytesCustomCeil(EByte-PByte, 2), "999 PB"}, + {"Ceil : bytes(1EB) with precision(2)", BytesCustomCeil(EByte, 2), "1 EB"}, + + {"Ceil : bytes(92160871366656) with precision(2)", BytesCustomCeil(92160871366656, 2), "92.17 TB"}, + {"Ceil : bytes(92160871366656) with precision(10)", BytesCustomCeil(92160871366656, 10), "92.1608713667 TB"}, + {"Ceil : bytes(92160871366656) with precision(3)", BytesCustomCeil(92160866656, 3), "92.161 GB"}, + {"Ceil : bytes(92160871366656) with precision(2)", BytesCustomCeil(92160866656, 2), "92.17 GB"}, + {"Ceil : bytes(92160871366656) with precision(0)", BytesCustomCeil(102160871366656, 0), "103 TB"}, + {"Ceil : bytes(92160871366656) with precision(20)", BytesCustomCeil(102160871366656, 20), "102.16087136665599643948 TB"}, + + {"Ceil : bytes(0) with precision(2)", IBytesCustomCeil(0, 2), "0 B"}, + {"Ceil : bytes(1) with precision(2)", IBytesCustomCeil(1, 2), "1 B"}, + + {"Ceil : bytes(803) with precision(2)", IBytesCustomCeil(803, 2), "803 B"}, + {"Ceil : bytes(1023) with precision(2)", IBytesCustomCeil(1023, 2), "1023 B"}, + {"Ceil : bytes(1024) with precision(2)", IBytesCustomCeil(1024, 2), "1 KiB"}, + + {"Ceil : bytes(1MB - 1) with precision(2)", IBytesCustomCeil(MiByte-IByte, 2), "1024 KiB"}, + {"Ceil : bytes(1MB) with precision(2)", IBytesCustomCeil(1024*1024, 2), "1 MiB"}, + {"Ceil : bytes(1GB - 1K) with precision(2)", IBytesCustomCeil(GiByte-KiByte, 2), "1024 MiB"}, + {"Ceil : bytes(1GB) with precision(2)", IBytesCustomCeil(GiByte, 2), "1 GiB"}, + {"Ceil : bytes(1TB - 1M) with precision(2)", IBytesCustomCeil(TiByte-MiByte, 2), "1024 GiB"}, + {"Ceil : bytes(1TB) with precision(2)", IBytesCustomCeil(TiByte, 2), "1 TiB"}, + {"Ceil : bytes(1PB - 1T) with precision(2)", IBytesCustomCeil(PiByte-TiByte, 2), "1023 TiB"}, + {"Ceil : bytes(1PB) with precision(2)", IBytesCustomCeil(PiByte, 2), "1 PiB"}, + {"Ceil : bytes(1PB - 1T) with precision(2)", IBytesCustomCeil(EiByte-PiByte, 2), "1023 PiB"}, + {"Ceil : bytes(1EiB) with precision(1)", IBytesCustomCeil(EiByte, 2), "1 EiB"}, + {"Ceil : bytes(5.5GiB) with precision(3)", IBytesCustomCeil(5.5*GiByte, 3), "5.5 GiB"}, + + {"Ceil : bytes(92160871366656) with precision(2)", IBytesCustomCeil(92160871366656, 2), "83.82 TiB"}, + {"Ceil : bytes(92160871366656) with precision(10)", IBytesCustomCeil(92160871366656, 10), "83.8198242188 TiB"}, + {"Ceil : bytes(92160871366656) with precision(3)", IBytesCustomCeil(92160866656, 3), "85.832 GiB"}, + {"Ceil : bytes(92160871366656) with precision(2)", IBytesCustomCeil(92160866656, 2), "85.84 GiB"}, + {"Ceil : bytes(92160871366656) with precision(0)", IBytesCustomCeil(102160871366656, 0), "93 TiB"}, + {"Ceil : bytes(92160871366656) with precision(20)", IBytesCustomCeil(102160871366656, 20), "92.91477123647928237915 TiB"}, }.validate(t) }