From 1be71edc9df498eaf16f4b213daff180ad97ab32 Mon Sep 17 00:00:00 2001 From: tw93 Date: Sat, 28 Feb 2026 10:02:34 +0800 Subject: [PATCH] fix: use Base-10 sizes and mdls logical size to match macOS Finder - Switch bytes_to_human (shell) and humanizeBytes (Go) from Base-2 (1024) to Base-10 (1000) to match Apple's storage calculation standard since Snow Leopard - Add proper decimal rounding instead of truncation - Use mdls kMDItemLogicalSize for .app bundles to avoid APFS clone file undercounting by du Fixes #511 --- cmd/analyze/format.go | 4 ++-- cmd/analyze/format_test.go | 18 +++++++++--------- lib/core/base.sh | 21 ++++++++++++--------- lib/core/file_ops.sh | 13 +++++++++++++ 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/cmd/analyze/format.go b/cmd/analyze/format.go index 5ef48d6..371539b 100644 --- a/cmd/analyze/format.go +++ b/cmd/analyze/format.go @@ -80,7 +80,7 @@ func humanizeBytes(size int64) string { if size < 0 { return "0 B" } - const unit = 1024 + const unit = 1000 if size < unit { return fmt.Sprintf("%d B", size) } @@ -90,7 +90,7 @@ func humanizeBytes(size int64) string { exp++ } value := float64(size) / float64(div) - return fmt.Sprintf("%.1f %cB", value, "KMGTPE"[exp]) + return fmt.Sprintf("%.1f %cB", value, "kMGTPE"[exp]) } func coloredProgressBar(value, maxValue int64, percent float64) string { diff --git a/cmd/analyze/format_test.go b/cmd/analyze/format_test.go index 7cfdf57..65a8333 100644 --- a/cmd/analyze/format_test.go +++ b/cmd/analyze/format_test.go @@ -63,15 +63,15 @@ func TestHumanizeBytes(t *testing.T) { {-100, "0 B"}, {0, "0 B"}, {512, "512 B"}, - {1023, "1023 B"}, - {1024, "1.0 KB"}, - {1536, "1.5 KB"}, - {10240, "10.0 KB"}, - {1048576, "1.0 MB"}, - {1572864, "1.5 MB"}, - {1073741824, "1.0 GB"}, - {1099511627776, "1.0 TB"}, - {1125899906842624, "1.0 PB"}, + {999, "999 B"}, + {1000, "1.0 kB"}, + {1500, "1.5 kB"}, + {10000, "10.0 kB"}, + {1000000, "1.0 MB"}, + {1500000, "1.5 MB"}, + {1000000000, "1.0 GB"}, + {1000000000000, "1.0 TB"}, + {1000000000000000, "1.0 PB"}, } for _, tt := range tests { diff --git a/lib/core/base.sh b/lib/core/base.sh index d0efe1a..e11c7f8 100644 --- a/lib/core/base.sh +++ b/lib/core/base.sh @@ -451,6 +451,7 @@ ensure_user_file() { # ============================================================================ # Convert bytes to human-readable format (e.g., 1.5GB) +# macOS (since Snow Leopard) uses Base-10 calculation (1 KB = 1000 bytes) bytes_to_human() { local bytes="$1" [[ "$bytes" =~ ^[0-9]+$ ]] || { @@ -458,15 +459,17 @@ bytes_to_human() { return 1 } - # GB: >= 1073741824 bytes - if ((bytes >= 1073741824)); then - printf "%d.%02dGB\n" $((bytes / 1073741824)) $(((bytes % 1073741824) * 100 / 1073741824)) - # MB: >= 1048576 bytes - elif ((bytes >= 1048576)); then - printf "%d.%01dMB\n" $((bytes / 1048576)) $(((bytes % 1048576) * 10 / 1048576)) - # KB: >= 1024 bytes (round up) - elif ((bytes >= 1024)); then - printf "%dKB\n" $(((bytes + 512) / 1024)) + # GB: >= 1,000,000,000 bytes + if ((bytes >= 1000000000)); then + local scaled=$(( (bytes * 100 + 500000000) / 1000000000 )) + printf "%d.%02dGB\n" $((scaled / 100)) $((scaled % 100)) + # MB: >= 1,000,000 bytes + elif ((bytes >= 1000000)); then + local scaled=$(( (bytes * 10 + 500000) / 1000000 )) + printf "%d.%01dMB\n" $((scaled / 10)) $((scaled % 10)) + # KB: >= 1,000 bytes (round up to nearest KB instead of decimal) + elif ((bytes >= 1000)); then + printf "%dKB\n" $(((bytes + 500) / 1000)) else printf "%dB\n" "$bytes" fi diff --git a/lib/core/file_ops.sh b/lib/core/file_ops.sh index fc5cfee..4f90cea 100644 --- a/lib/core/file_ops.sh +++ b/lib/core/file_ops.sh @@ -503,6 +503,19 @@ get_path_size_kb() { echo "0" return } + + # For .app bundles, prefer mdls logical size as it matches Finder + # (APFS clone/sparse files make 'du' severely underreport apps like Xcode) + if [[ "$path" == *.app || "$path" == *.app/ ]]; then + local mdls_size + mdls_size=$(mdls -name kMDItemLogicalSize -raw "$path" 2> /dev/null || true) + if [[ "$mdls_size" =~ ^[0-9]+$ && "$mdls_size" -gt 0 ]]; then + # Return in KB + echo "$((mdls_size / 1024))" + return + fi + fi + local size size=$(command du -skP "$path" 2> /dev/null | awk 'NR==1 {print $1; exit}' || true)