將位元組數轉為「KB、MB、GB」等人類可讀格式是常見需求。本文提供兩種風格迥異但效果相同的實現:對數數學法 (一行式) 與 迭代除法 (直觀易讀),並分析效能與邊界情況。
<?php
/**
* 位元組轉人類可讀大小 (對數法)
* @param int|float $bytes 位元組數
* @param int $decimals 小數位數
* @return string
*/
function humanFilesizeLog(float $bytes, int $decimals = 2): string {
if ($bytes <= 0) return '0 B';
$units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
$exp = (int)floor(log($bytes, 1024));
$exp = min($exp, count($units) - 1); // 防止超出陣列
return round($bytes / (1024 ** $exp), $decimals) . ' ' . $units[$exp];
}
// 測試
echo humanFilesizeLog(0); // 0 B
echo humanFilesizeLog(500); // 500 B
echo humanFilesizeLog(1024); // 1 KB
echo humanFilesizeLog(1536); // 1.5 KB
echo humanFilesizeLog(1024**2); // 1 MB
echo humanFilesizeLog(1024**3 * 1.5); // 1.5 GB
echo humanFilesizeLog(1024**5); // 1 PB
?>
log($bytes, 1024) 計算以 1024 為底的對數,直接得到單位指數 (0=B, 1=KB, 2=MB...)。floor() 取整,min() 限制最大指數防止陣列越界。1024 ** $exp (PHP 5.6+) 計算除數,等同 pow(1024, $exp)。<?php
/**
* 位元組轉人類可讀大小 (迭代法)
* @param int|float $bytes
* @param int $decimals
* @return string
*/
function humanFilesizeIter(float $bytes, int $decimals = 2): string {
$units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
$i = 0;
while ($bytes >= 1024 && $i < count($units) - 1) {
$bytes /= 1024;
$i++;
}
return round($bytes, $decimals) . ' ' . $units[$i];
}
log, pow),極大數值下數值穩定性略好。KiB, MiB (IEC 標準 1024) vs KB, MB (SI 標準 1000) 切換邏輯。<?php
$testValues = [0, 500, 1024, 1024**2, 1024**3, 1024**4, 1024**5, 1024**6];
$iterations = 100000;
$start = hrtime(true);
foreach ($testValues as $v) for ($i=0;$i<$iterations;$i++) humanFilesizeLog($v);
echo "Log 法: " . (hrtime(true)-$start)/1e9 . "s\n";
$start = hrtime(true);
foreach ($testValues as $v) for ($i=0;$i<$iterations;$i++) humanFilesizeIter($v);
echo "Iter 法: " . (hrtime(true)-$start)/1e9 . "s\n";
?>
典型輸出:
Log 法: 0.18s
Iter 法: 0.12s
結論:迭代法略快 (避免 log/pow 函數調用開銷),但差異微乎其微 (納秒級)。**選擇原則:團隊偏好數學優雅用 Log 法,偏好直觀維護用 Iter 法。**
<?php
function humanFilesize(float $bytes, int $decimals = 2, bool $iec = true): string {
$base = $iec ? 1024 : 1000;
$units = $iec
? ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
: ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
if ($bytes < $base) return $bytes . ' ' . $units[0];
$exp = min((int)floor(log($bytes, $base)), count($units) - 1);
return round($bytes / ($base ** $exp), $decimals) . ' ' . $units[$exp];
}
echo humanFilesize(1000); // 1000 B (IEC) / 1 KB (SI)
echo humanFilesize(1024); // 1 KiB (IEC) / 1.02 KB (SI)
echo humanFilesize(1000**3); // 953.67 MiB (IEC) / 1 GB (SI)
?>
0 B (或拋出異常視業務而定)。min() 限制指數,返回最大單位 (如 12345 YB)。round() 處理小數,避免 1.999999 KB 顯示問題。float 兼容 int,大檔案 (>2GB) 於 32 位系統需用 float 或 string 傳入。App\Utils\FileHelper::humanSize()。decimals 為常數 (如 FILE_SIZE_DECIMALS = 2) 統一風格。Str::formatFileSize() (內建 IEC 標準)。NumberFormatter::format() 配合 NumberFormatter::FILE_SIZE。無論選哪種實現,核心都是「以 1024 (或 1000) 為底換算單位」。理解兩種算法本質,能讓你在 Code Review 時自信判斷代碼品質,也能根據團隊風格靈活選擇。