36
function humanFileSize($size)
{
    if ($size >= 1073741824) {
      $fileSize = round($size / 1024 / 1024 / 1024,1) . 'GB';
    } elseif ($size >= 1048576) {
        $fileSize = round($size / 1024 / 1024,1) . 'MB';
    } elseif($size >= 1024) {
        $fileSize = round($size / 1024,1) . 'KB';
    } else {
        $fileSize = $size . ' bytes';
    }
    return $fileSize;
}

... works great except: I can't manually choose in what format I need to display, say i want to show in MB only whatever the file size is. Currently if its in the GB range, it would only show in GB.

Also, how do I limit the decimal to 2?

eozzy
  • 58,300
  • 96
  • 249
  • 396

12 Answers12

56

Try something like this:

function humanFileSize($size,$unit="") {
  if( (!$unit && $size >= 1<<30) || $unit == "GB")
    return number_format($size/(1<<30),2)."GB";
  if( (!$unit && $size >= 1<<20) || $unit == "MB")
    return number_format($size/(1<<20),2)."MB";
  if( (!$unit && $size >= 1<<10) || $unit == "KB")
    return number_format($size/(1<<10),2)."KB";
  return number_format($size)." bytes";
}
Niet the Dark Absol
  • 301,028
  • 70
  • 427
  • 540
  • 2
    It should rather be `$unit == "GB" || !$unit && $size >= 1<<30`. – Gumbo Mar 03 '13 at 17:01
  • @Gumbo Why? What difference does it make? Actually, that would break it for non-string parameters like `0`. – Niet the Dark Absol Mar 03 '13 at 17:03
  • Your solution requires the value to be at least 1 of the unit’s size, i.e. there is no way to get a value that is less than 1 like `0.5 GB`. In such a case the result is displayed in bytes. – Gumbo Mar 03 '13 at 17:18
  • @Nimbuz This is probably because it's assuming 1MB == 1000000 Bytes (and it's actually 1024*1024 Bytes). – Camilo Martin Dec 17 '13 at 23:09
  • I think this answer isn't very good. It's overly complicated from a semantic standpoint (why use `1<<10` instead of `1024`?) and it doesn't cover units larger than GB. – Krzysztof Karski Jun 07 '17 at 10:05
  • 1
    @KrzysztofKarski Your feedback is welcome, but `1<<10` is used instead of `1024` for the same reason that `1<<30` is used instead of `1073741824` - it conveys more meaning. Furthermore, adding bigger units is trivial, simply add in the variant with `1<<40` and `"TB"`, and beyond. The point of this answer isn't to be a 100% complete solution, hence the "Try something **like this**:" text. – Niet the Dark Absol Jun 07 '17 at 10:10
  • Why not `pow(1024, 3)` then? It states your intent way more clearly than arriving at a desired number through bit shifting... – Krzysztof Karski Jun 07 '17 at 12:27
  • @KrzysztofKarski Because bit-shifting is the definition? It's also significantly more efficient. – Niet the Dark Absol Jun 07 '17 at 12:39
  • @NiettheDarkAbsol that's premature optimization which is rendered moot by multiple logic branches. – Krzysztof Karski Jun 12 '17 at 07:54
42

There is great example by Jeffrey Sambells:

function human_filesize($bytes, $dec = 2) 
{
    $size   = array('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
    $factor = floor((strlen($bytes) - 1) / 3);

    return sprintf("%.{$dec}f", $bytes / pow(1024, $factor)) . @$size[$factor];
}

print human_filesize(filesize('example.zip'));
Vaidas
  • 818
  • 9
  • 22
  • 1
    Nice, but I would add `if($factor==0) $dec=0;` – Artjom Kurapov Nov 20 '14 at 18:37
  • its not accurate, 4,281,016 bytes gives 4.08 MB while google says 4.28102 megabytes –  Mar 31 '15 at 12:58
  • i guess google is wrong tinking 1mb = 1000000 byes, it should b 1024*1024 –  Mar 31 '15 at 13:02
  • 5
    Google is not wrong. 1MB = 1000000B. 1MiB = 1048576B. They're just following the latest IEC standards for units: https://en.wikipedia.org/wiki/Binary_prefix – Krzysztof Karski Oct 20 '16 at 07:59
  • 1
    For the return line you can also use: `return sprintf("%.{$dec}f %s", $bytes / (1024 ** $factor), $size[$factor]);` It's a bit cleaner. – maki10 Oct 30 '19 at 12:23
  • 1
    @maki10 Thanks! I've been writing `pow()` for about 10 years now, and this is the first time I see the `**` operator. It's incredible, since it's available since version 5.6 (: I also agree about the **sprintf shortcut**. Have You submitted it as an edit? If not, please do so. – Márton Tamás Apr 07 '20 at 10:07
  • For big numbers the string representation on PHP is being expressed with scientific notation (`1.05E+14` for example). That would break the function using `strlen`. I'd add something of this sort to remedy the issue: `$bytes = number_format($bytes, 0, '.', '');` – Aradmey Aug 03 '20 at 12:02
16

I'm using this method:

function byteConvert($bytes)
{
    if ($bytes == 0)
        return "0.00 B";

    $s = array('B', 'KB', 'MB', 'GB', 'TB', 'PB');
    $e = floor(log($bytes, 1024));

    return round($bytes/pow(1024, $e), 2).$s[$e];
}

works great in o(1).

Alexander Farber
  • 18,345
  • 68
  • 208
  • 375
Roey
  • 1,440
  • 19
  • 16
5

A pretty short 3 lines method that I use (1024 = 1KB) and supports from KB to YB is the following one:

<?php 

/**
 * Converts a long string of bytes into a readable format e.g KB, MB, GB, TB, YB
 * 
 * @param {Int} num The number of bytes.
 */
function readableBytes($bytes) {
    $i = floor(log($bytes) / log(1024));

    $sizes = array('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');

    return sprintf('%.02F', $bytes / pow(1024, $i)) * 1 . ' ' . $sizes[$i];
}

// "1000 B"
echo readableBytes(1000);

// "9.42 MB"
echo readableBytes(9874321);

// "9.31 GB"
// The number of bytes as a string is accepted as well
echo readableBytes("10000000000");

// "648.37 TB"
echo readableBytes(712893712304234);

// "5.52 PB"
echo readableBytes(6212893712323224);

More info about these methods on this article.

Carlos Delgado
  • 2,552
  • 4
  • 20
  • 42
4

To expand on Vaidas' answer, here's how you should do it to account for the new IEC standards:

function human_readable_bytes($bytes, $decimals = 2, $system = 'binary')
{
    $mod = ($system === 'binary') ? 1024 : 1000;

    $units = array(
        'binary' => array(
            'B',
            'KiB',
            'MiB',
            'GiB',
            'TiB',
            'PiB',
            'EiB',
            'ZiB',
            'YiB',
        ),
        'metric' => array(
            'B',
            'kB',
            'MB',
            'GB',
            'TB',
            'PB',
            'EB',
            'ZB',
            'YB',
        ),
    );

    $factor = floor((strlen($bytes) - 1) / 3);

    return sprintf("%.{$decimals}f%s", $bytes / pow($mod, $factor), $units[$system][$factor]);
}

Technically, according to the specifications for storage devices and such you should use the metric system as default (that's why Google converter shows kB -> MB as mod 1000 instead of 1024).

Krzysztof Karski
  • 579
  • 1
  • 10
  • 16
1

You can modify your function to fullfil both your need to force a unit if given and adjust the precision.

function humanFileSize($size, $precision = 1, $show = "")
{
    $b = $size;
    $kb = round($size / 1024, $precision);
    $mb = round($kb / 1024, $precision);
    $gb = round($mb / 1024, $precision);

    if($kb == 0 || $show == "B") {
        return $b . " bytes";
    } else if($mb == 0 || $show == "KB") {
        return $kb . "KB";
    } else if($gb == 0 || $show == "MB") {
        return $mb . "MB";
    } else {
        return $gb . "GB";
    }
}

//Test with different values
echo humanFileSize(1038) . "<br />";    
echo humanFileSize(103053, 0) . "<br />";
echo humanFileSize(103053) . "<br />";
echo humanFileSize(1030544553) . "<br />";
echo humanFileSize(1030534053405, 2, "GB") . "<br />";  ;
automaticoo
  • 868
  • 7
  • 24
1
function getHumanReadableSize($size, $unit = null, $decemals = 2) {
    $byteUnits = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
    if (!is_null($unit) && !in_array($unit, $byteUnits)) {
        $unit = null;
    }
    $extent = 1;
    foreach ($byteUnits as $rank) {
        if ((is_null($unit) && ($size < $extent <<= 10)) || ($rank == $unit)) {
            break;
        }
    }
    return number_format($size / ($extent >> 10), $decemals) . $rank;
}

If php version below 5.4 use $byteUnits = array('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');

1

I wanted a function that returned filesizes like Windows does, and surprisingly I could find none at all. Even worse, some here and elsewhere are broken in that they assume 1KB = 1000B.

So I coded one! Plus two helper functions. Here they are:

// Returns a size in a human-readable form from a byte count.
function humanSize($bytes)
{
    if ($bytes < 1024) return "$bytes Bytes";

    $units = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];

    foreach ($units as $i => $unit)
    {
        // The reason for this threshold is to avoid e.g., "1000 KB",
        // instead jumping from e.g., "999 KB" to "0.97 MB".
        $multiplier = pow(1024, $i + 1);
        $threshold = $multiplier * 1000;

        if ($bytes < $threshold)
        {
            $size = formatToMinimumDigits($bytes / $multiplier, false);
            return "$size $unit";
        }
    }
}

// Efficiently calculates how many digits the integer portion of a number has.
function digits($number)
{
    // Yes, I could convert to string and count the characters,
    // but this is faster and cooler.
    $log = log10($number);
    if ($log < 0) return 1;
    return floor($log) + 1;
}

// Formats a number to a minimum amount of digits.
// In other words, makes sure that a number has at least $digits on it, even if
// that means introducing redundant decimal zeroes at the end, or rounding the
// ones present exceeding the $digits count when combined with the integers.
// For example:
//     formatToMinimumDigits(10)           // 10.0
//     formatToMinimumDigits(1.1)          // 1.10
//     formatToMinimumDigits(12.34)        // 12.3
//     formatToMinimumDigits(1.234)        // 1.23
//     formatToMinimumDigits(1.203)        // 1.20
//     formatToMinimumDigits(123.4)        // 123
//     formatToMinimumDigits(100)          // 100
//     formatToMinimumDigits(1000)         // 1000
//     formatToMinimumDigits(1)            // 1.00
//     formatToMinimumDigits(1.002)        // 1.00
//     formatToMinimumDigits(1.005)        // 1.01
//     formatToMinimumDigits(1.005, false) // 1.00
// This is primarily useful for generating human-friendly numbers.
function formatToMinimumDigits($value, $round = true, $digits = 3)
{
    $integers = floor($value);

    $decimalsNeeded = $digits - digits($integers);

    if ($decimalsNeeded < 1)
    {
        return $integers;
    }
    else
    {
        if ($round)
        {
            // This relies on implicit type casting of float to string.
            $parts = explode('.', round($value, $decimalsNeeded));
            // We re-declare the integers because they may change
            // after we round the number.
            $integers = $parts[0];
        }
        else
        {
            // Again, implicit type cast to string.
            $parts = explode('.', $value);
        }

        // And because of the implicit type cast, we must guard against
        // 1.00 becoming 1, thus not exploding the second half of it.
        $decimals = isset($parts[1]) ? $parts[1] : '0';
        $joined = "$integers.$decimals".str_repeat('0', $digits);
        return substr($joined, 0, $digits + 1);
    }
}

Usage is as simple as humanSize(123456789).

Camilo Martin
  • 34,128
  • 20
  • 104
  • 150
1
function bytesToHuman($bytes)
{
    $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
    for ($i = 0; $bytes > 1024; $i++) $bytes /= 1024;
    return round($bytes, 2) . ' ' . $units[$i];
}

Credit: https://laracasts.com/discuss/channels/laravel/human-readable-file-size-and-time?page=1#reply=115796

kdion4891
  • 869
  • 4
  • 11
1

This is how I use, it's clean and simple. Also can be used like this:

public function getHumanReadableFilesize(int $bytes, int $dec = 2): string
{
    $size   = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
    $factor = floor((strlen($bytes) - 1) / 3);

    return sprintf("%.{$dec}f %s", ($bytes / (1024 ** $factor)), $size[$factor]);
}

Thanks @Márton Tamás for suggestion to add like comment.

maki10
  • 500
  • 2
  • 11
0

Here's my custom function for displaying human readable file size:

function getHumanReadableSize($bytes) {
  if ($bytes > 0) {
    $base = floor(log($bytes) / log(1024));
    $units = array("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"); //units of measurement
    return number_format(($bytes / pow(1024, floor($base))), 3) . " $units[$base]";
  } else return "0 bytes";
}
1000Gbps
  • 969
  • 1
  • 21
  • 32
-1

Here is a working function managing till Yottabyte:

function DisplayFileSize($size, $unit = false, $precision = 2){

$b = $size;
$kb = round($size / 1024, $precision);
$mb = round($kb / 1024, $precision);
$gb = round($mb / 1024, $precision);
$tb = round($gb / 1024, $precision);
$pb = round($tb / 1024, $precision);
$eb = round($pb / 1024, $precision);
$zb = round($eb / 1024, $precision);
$yb = round($zb / 1024, $precision);

if((!$unit && floor($kb) == 0) || $unit == "b") {
    return array("value" => FormatNumber($b), "unit" => "bytes");
} else if((!$unit && floor($mb) == 0) || $unit == "kb") {
    return array("value" => FormatNumber($kb, 2), "unit" => "Kb");
} else if((!$unit && floor($gb) == 0) || $unit == "mb") {
    return array("value" => FormatNumber($mb, 2), "unit" => "Mb");
 } else if((!$unit && floor($tb) == 0) || $unit == "gb") {
     return array("value" => FormatNumber($gb, 2), "unit" => "Gb");
 } else if((!$unit && floor($pb) == 0) || $unit == "tb") {
     return array("value" => FormatNumber($tb, 2), "unit" => "Tb");
 } else if((!$unit && floor($eb) == 0) || $unit == "pb") {
    return array("value" => FormatNumber($pb, 2), "unit" => "Pb");
 } else if((!$unit && floor($zb) == 0) || $unit == "eb") {
     return array("value" => FormatNumber($eb, 2), "unit" => "Eb");
 } else if((!$unit && floor($yb) == 0) || $unit == "zb") {
     return array("value" => FormatNumber($zb, 2), "unit" => "Zb");
} else {
    return array("value" => FormatNumber($yb, 2), "unit" => "Yb");
}

}

Matbe81
  • 1
  • 3