From 14a2e4682435758fc1fe2688a62ca42023dc5b76 Mon Sep 17 00:00:00 2001 From: Kamyar Mirzavaziri Date: Sun, 14 Jan 2024 19:56:02 +0330 Subject: [PATCH] Fix HumanSizeWithPrecision invalid scientific notation Signed-off-by: Kamyar Mirzavaziri --- size.go | 26 +++++++++++++------ size_test.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 8 deletions(-) diff --git a/size.go b/size.go index c245a89..4d1ba77 100644 --- a/size.go +++ b/size.go @@ -2,6 +2,7 @@ package units import ( "fmt" + "math" "strconv" "strings" ) @@ -37,28 +38,37 @@ var ( binaryAbbrs = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"} ) -func getSizeAndUnit(size float64, base float64, _map []string) (float64, string) { +func getSizeAndUnitIndex(size float64, base float64, unitsCount int) (float64, int) { i := 0 - unitsLimit := len(_map) - 1 - for size >= base && i < unitsLimit { + for size >= base && i < unitsCount-1 { size = size / base i++ } - return size, _map[i] + return size, i } // CustomSize returns a human-readable approximation of a size // using custom format. func CustomSize(format string, size float64, base float64, _map []string) string { - size, unit := getSizeAndUnit(size, base, _map) - return fmt.Sprintf(format, size, unit) + size, unitIndex := getSizeAndUnitIndex(size, base, len(_map)) + return fmt.Sprintf(format, size, _map[unitIndex]) } // HumanSizeWithPrecision allows the size to be in any precision, // instead of 4 digit precision used in units.HumanSize. func HumanSizeWithPrecision(size float64, precision int) string { - size, unit := getSizeAndUnit(size, 1000.0, decimapAbbrs) - return fmt.Sprintf("%.*g%s", precision, size, unit) + const base = 1000 + + unitsCount := len(decimapAbbrs) + + size, unitIndex := getSizeAndUnitIndex(size, base, unitsCount) + + if unitIndex < unitsCount-1 && math.Pow(10, float64(precision))-.5 <= size { + size = size / base + unitIndex++ + } + + return fmt.Sprintf("%.*g%s", precision, size, decimapAbbrs[unitIndex]) } // HumanSize returns a human-readable approximation of a size diff --git a/size_test.go b/size_test.go index f9b1d59..3eefa95 100644 --- a/size_test.go +++ b/size_test.go @@ -82,6 +82,78 @@ func TestHumanSize(t *testing.T) { assertEquals(t, "1e+04YB", HumanSize(float64(10000000000000*PB))) } +func TestHumanSizeWithPrecision(t *testing.T) { + assertEquals(t, "1kB", HumanSizeWithPrecision(1000, 4)) + assertEquals(t, "1.024kB", HumanSizeWithPrecision(1024, 4)) + assertEquals(t, "1MB", HumanSizeWithPrecision(1000000, 4)) + assertEquals(t, "1.049MB", HumanSizeWithPrecision(1048576, 4)) + assertEquals(t, "2MB", HumanSizeWithPrecision(2*MB, 4)) + assertEquals(t, "3.42GB", HumanSizeWithPrecision(float64(3.42*GB), 4)) + assertEquals(t, "5.372TB", HumanSizeWithPrecision(float64(5.372*TB), 4)) + assertEquals(t, "2.22PB", HumanSizeWithPrecision(float64(2.22*PB), 4)) + assertEquals(t, "1e+04YB", HumanSizeWithPrecision(float64(10000000000000*PB), 4)) + + assertEquals(t, "1kB", HumanSizeWithPrecision(1000, 3)) + assertEquals(t, "1.02kB", HumanSizeWithPrecision(1024, 3)) + assertEquals(t, "1MB", HumanSizeWithPrecision(1000000, 3)) + assertEquals(t, "1.05MB", HumanSizeWithPrecision(1048576, 3)) + assertEquals(t, "2MB", HumanSizeWithPrecision(2*MB, 3)) + assertEquals(t, "3.42GB", HumanSizeWithPrecision(float64(3.42*GB), 3)) + assertEquals(t, "5.37TB", HumanSizeWithPrecision(float64(5.372*TB), 3)) + assertEquals(t, "2.22PB", HumanSizeWithPrecision(float64(2.22*PB), 3)) + assertEquals(t, "1e+04YB", HumanSizeWithPrecision(float64(10000000000000*PB), 3)) + assertEquals(t, "1GB", HumanSizeWithPrecision(float64(999.5*MB), 3)) + assertEquals(t, "1GB", HumanSizeWithPrecision(float64(999.9*MB), 3)) + assertEquals(t, "1GB", HumanSizeWithPrecision(float64(1000*MB), 3)) + + assertEquals(t, "1kB", HumanSizeWithPrecision(1000, 2)) + assertEquals(t, "1kB", HumanSizeWithPrecision(1024, 2)) + assertEquals(t, "1MB", HumanSizeWithPrecision(1000000, 2)) + assertEquals(t, "1MB", HumanSizeWithPrecision(1048576, 2)) + assertEquals(t, "2MB", HumanSizeWithPrecision(2*MB, 2)) + assertEquals(t, "3.4GB", HumanSizeWithPrecision(float64(3.42*GB), 2)) + assertEquals(t, "5.4TB", HumanSizeWithPrecision(float64(5.372*TB), 2)) + assertEquals(t, "2.2PB", HumanSizeWithPrecision(float64(2.22*PB), 2)) + assertEquals(t, "1e+04YB", HumanSizeWithPrecision(float64(10000000000000*PB), 2)) + assertEquals(t, "0.1GB", HumanSizeWithPrecision(float64(99.5*MB), 2)) + assertEquals(t, "0.1GB", HumanSizeWithPrecision(float64(99.9*MB), 2)) + assertEquals(t, "0.1GB", HumanSizeWithPrecision(float64(100*MB), 2)) + assertEquals(t, "0.9GB", HumanSizeWithPrecision(float64(900*MB), 2)) + assertEquals(t, "0.95GB", HumanSizeWithPrecision(float64(950*MB), 2)) + assertEquals(t, "0.96GB", HumanSizeWithPrecision(float64(960*MB), 2)) + assertEquals(t, "1GB", HumanSizeWithPrecision(float64(996*MB), 2)) + assertEquals(t, "1GB", HumanSizeWithPrecision(float64(999.5*MB), 2)) + assertEquals(t, "1GB", HumanSizeWithPrecision(float64(999.9*MB), 2)) + assertEquals(t, "1GB", HumanSizeWithPrecision(float64(1000*MB), 2)) + + // HumanSizeWithPrecision does not work with precision 1 very well + assertEquals(t, "1kB", HumanSizeWithPrecision(1000, 1)) + assertEquals(t, "1kB", HumanSizeWithPrecision(1024, 1)) + assertEquals(t, "1MB", HumanSizeWithPrecision(1000000, 1)) + assertEquals(t, "1MB", HumanSizeWithPrecision(1048576, 1)) + assertEquals(t, "2MB", HumanSizeWithPrecision(2*MB, 1)) + assertEquals(t, "3GB", HumanSizeWithPrecision(float64(3.42*GB), 1)) + assertEquals(t, "6TB", HumanSizeWithPrecision(float64(5.972*TB), 1)) + assertEquals(t, "2PB", HumanSizeWithPrecision(float64(2.22*PB), 1)) + assertEquals(t, "1e+04YB", HumanSizeWithPrecision(float64(10000000000000*PB), 1)) + assertEquals(t, "0.009GB", HumanSizeWithPrecision(float64(9.5*MB), 1)) + assertEquals(t, "0.01GB", HumanSizeWithPrecision(float64(9.9*MB), 1)) + assertEquals(t, "0.01GB", HumanSizeWithPrecision(float64(10*MB), 1)) + assertEquals(t, "0.09GB", HumanSizeWithPrecision(float64(90*MB), 1)) + assertEquals(t, "0.05GB", HumanSizeWithPrecision(float64(50*MB), 1)) + assertEquals(t, "0.1GB", HumanSizeWithPrecision(float64(96*MB), 1)) + assertEquals(t, "0.1GB", HumanSizeWithPrecision(float64(99.5*MB), 1)) + assertEquals(t, "0.1GB", HumanSizeWithPrecision(float64(99.9*MB), 1)) + assertEquals(t, "0.1GB", HumanSizeWithPrecision(float64(100*MB), 1)) + assertEquals(t, "0.9GB", HumanSizeWithPrecision(float64(900*MB), 1)) + assertEquals(t, "0.9GB", HumanSizeWithPrecision(float64(950*MB), 1)) + assertEquals(t, "1GB", HumanSizeWithPrecision(float64(960*MB), 1)) + assertEquals(t, "1GB", HumanSizeWithPrecision(float64(996*MB), 1)) + assertEquals(t, "1GB", HumanSizeWithPrecision(float64(999.5*MB), 1)) + assertEquals(t, "1GB", HumanSizeWithPrecision(float64(999.9*MB), 1)) + assertEquals(t, "1GB", HumanSizeWithPrecision(float64(1000*MB), 1)) +} + func TestFromHumanSize(t *testing.T) { assertSuccessEquals(t, 0, FromHumanSize, "0") assertSuccessEquals(t, 0, FromHumanSize, "0b")