format: use binary prefixes for HumanBytes file size formatting

Change HumanBytes to use binary prefixes (1024-based) instead of decimal
(1000-based) for file size formatting. This fixes the rounding error where
3072 bytes was displayed as '3.1 KB' instead of '3 KB'.

The decimal constants (KiloByte, MegaByte, etc.) are preserved for other
uses like buffer sizes and memory capacity.

Fixes #13405
This commit is contained in:
Nathan Nguyen 2025-12-29 22:05:26 -05:00
parent 18fdcc94e5
commit 524486b412
2 changed files with 35 additions and 28 deletions

View File

@ -8,32 +8,37 @@ import (
const ( const (
Byte = 1 Byte = 1
// Decimal prefixes (1000-based) - used for buffer sizes, memory capacity
KiloByte = Byte * 1000 KiloByte = Byte * 1000
MegaByte = KiloByte * 1000 MegaByte = KiloByte * 1000
GigaByte = MegaByte * 1000 GigaByte = MegaByte * 1000
TeraByte = GigaByte * 1000 TeraByte = GigaByte * 1000
// Binary prefixes (1024-based) - used for file sizes
KibiByte = Byte * 1024 KibiByte = Byte * 1024
MebiByte = KibiByte * 1024 MebiByte = KibiByte * 1024
GibiByte = MebiByte * 1024 GibiByte = MebiByte * 1024
TebiByte = GibiByte * 1024
) )
// HumanBytes formats bytes using binary prefixes (1024-based) with short unit names.
// This follows the convention used by most file systems and tools for file sizes.
func HumanBytes(b int64) string { func HumanBytes(b int64) string {
var value float64 var value float64
var unit string var unit string
switch { switch {
case b >= TeraByte: case b >= TebiByte:
value = float64(b) / TeraByte value = float64(b) / TebiByte
unit = "TB" unit = "TB"
case b >= GigaByte: case b >= GibiByte:
value = float64(b) / GigaByte value = float64(b) / GibiByte
unit = "GB" unit = "GB"
case b >= MegaByte: case b >= MebiByte:
value = float64(b) / MegaByte value = float64(b) / MebiByte
unit = "MB" unit = "MB"
case b >= KiloByte: case b >= KibiByte:
value = float64(b) / KiloByte value = float64(b) / KibiByte
unit = "KB" unit = "KB"
default: default:
return fmt.Sprintf("%d B", b) return fmt.Sprintf("%d B", b)

View File

@ -14,32 +14,34 @@ func TestHumanBytes(t *testing.T) {
// Test bytes (B) // Test bytes (B)
{0, "0 B"}, {0, "0 B"},
{1, "1 B"}, {1, "1 B"},
{999, "999 B"}, {1023, "1023 B"},
// Test kilobytes (KB) // Test kilobytes (KB) - binary prefix (1024-based)
{1000, "1 KB"}, {1024, "1 KB"},
{1500, "1.5 KB"}, {1536, "1.5 KB"},
{999999, "999 KB"}, {3072, "3 KB"},
{4096, "4 KB"},
{10240, "10 KB"},
// Test megabytes (MB) // Test megabytes (MB) - binary prefix (1024-based)
{1000000, "1 MB"}, {1048576, "1 MB"},
{1500000, "1.5 MB"}, {1572864, "1.5 MB"},
{999999999, "999 MB"}, {10485760, "10 MB"},
// Test gigabytes (GB) // Test gigabytes (GB) - binary prefix (1024-based)
{1000000000, "1 GB"}, {1073741824, "1 GB"},
{1500000000, "1.5 GB"}, {1610612736, "1.5 GB"},
{999999999999, "999 GB"}, {10737418240, "10 GB"},
// Test terabytes (TB) // Test terabytes (TB) - binary prefix (1024-based)
{1000000000000, "1 TB"}, {1099511627776, "1 TB"},
{1500000000000, "1.5 TB"}, {1649267441664, "1.5 TB"},
{1999999999999, "2.0 TB"}, {2199023255552, "2 TB"},
// Test fractional values // Test fractional values
{1234, "1.2 KB"}, {1280, "1.2 KB"},
{1234567, "1.2 MB"}, {1310720, "1.2 MB"},
{1234567890, "1.2 GB"}, {1342177280, "1.2 GB"},
} }
for _, tc := range tests { for _, tc := range tests {