Skip to content

Instantly share code, notes, and snippets.

@liunian
Last active October 6, 2023 20:48
Show Gist options
  • Star 50 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save liunian/9338301 to your computer and use it in GitHub Desktop.
Save liunian/9338301 to your computer and use it in GitHub Desktop.
Human Readable File Size with PHP
<?php
# http://jeffreysambells.com/2012/10/25/human-readable-filesize-php
function human_filesize($bytes, $decimals = 2) {
$size = array('B','kB','MB','GB','TB','PB','EB','ZB','YB');
$factor = floor((strlen($bytes) - 1) / 3);
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$size[$factor];
}
echo human_filesize(filesize('example.zip'));
@AjmalPraveeN
Copy link

AjmalPraveeN commented Jul 20, 2018

The below code is not my code, (I want to know which is better, Which is faster and reliable) ? can you please someone suggest me a Good code?

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";
}

@kdion4891
Copy link

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

@sobeaa
Copy link

sobeaa commented May 6, 2020

I took what I consider to be the best ideas in this thread and made what I consider the most appealing implementation of this problem. Feel free to use as you wish.

class Files
{
    const BYTE_UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
    const BYTE_PRECISION = [0, 0, 1, 2, 2, 3, 3, 4, 4];
    const BYTE_NEXT = 1024;

    /**
     * Convert bytes to be human readable.
     *
     * @param int      $bytes     Bytes to make readable
     * @param int|null $precision Precision of rounding
     *
     * @return string Human readable bytes
     */
    public static function HumanReadableBytes ($bytes, $precision = null)
    {
        for ($i = 0; ($bytes / self::BYTE_NEXT) >= 0.9 && $i < count(self::BYTE_UNITS); $i++) $bytes /= self::BYTE_NEXT;
        return round($bytes, is_null($precision) ? self::BYTE_PRECISION[$i] : $precision) . self::BYTE_UNITS[$i];
    }
}

@VijayS1
Copy link

VijayS1 commented Nov 25, 2020

I took what I consider to be the best ideas in this thread and made what I consider the most appealing implementation of this problem. Feel free to use as you wish.

class Files
{
    const BYTE_UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
    const BYTE_PRECISION = [0, 0, 1, 2, 2, 3, 3, 4, 4];
    const BYTE_NEXT = 1024;

    /**
     * Convert bytes to be human readable.
     *
     * @param int      $bytes     Bytes to make readable
     * @param int|null $precision Precision of rounding
     *
     * @return string Human readable bytes
     */
    public static function HumanReadableBytes ($bytes, $precision = null)
    {
        for ($i = 0; ($bytes / self::BYTE_NEXT) >= 0.9 && $i < count(self::BYTE_UNITS); $i++) $bytes /= self::BYTE_NEXT;
        return round($bytes, is_null($precision) ? self::BYTE_PRECISION[$i] : $precision) . self::BYTE_UNITS[$i];
    }
}

You have to add this line for typecasting $bytes or else you will get tons of PHP NOTICEs about malformed numbers
$bytes = (int) $bytes; //typecast to int to suppress PHP NOTICE

Anyway I edited it to use the MiB suffixes.

  const BYTE_UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
  const BYTE_PRECISION = [0, 0, 1, 2, 2, 3, 3, 4, 4];
  const BYTE_NEXT = 1024;
  public function fileSizeInfo($bytes, $precision = null) {
  	$bytes = (int) $bytes; //typecast to int to suppress PHP NOTICE
  	for ($i = 0; ($bytes / self::BYTE_NEXT) >= 0.9 && $i < count(self::BYTE_UNITS); $i++) $bytes /= self::BYTE_NEXT;
  	return round($bytes, is_null($precision) ? self::BYTE_PRECISION[$i] : (int)$precision) . self::BYTE_UNITS[$i];
  }

@ve3
Copy link

ve3 commented Jan 4, 2021

Even better

function MakeReadable($bytes) {
    $i = floor(log($bytes, 1024));
    return round($bytes / pow(1024, $i), [0,0,2,2,3][$i]).['B','kB','MB','GB','TB'][$i];
}

Can cause error Division by zero. if bytes enter is 0.

@crabmusket
Copy link

crabmusket commented Apr 12, 2021

Enterprise-grade OOP version 😁 (requires PHP 7.4)

  • comes with options built-in
  • currently uses binary prefixes instead of colloquial ones
class BytesForHumans
{
    public const FAMILIAR_UNIT_SCALE = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
    public const PEDANTIC_UNIT_SCALE = ['B', 'kiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];

    public string $formatString = "%g%s";
    public int $logBase = 1024;
    public int $maxDecimalPlaces = 2;
    public array $unitScale = self::PEDANTIC_UNIT_SCALE;

    /** Create a BytesForHumans from a number of bytes. Fractional bytes are not allowed. */
    public static function fromBytes(int $bytes)
    {
        if ($bytes < 0) {
            throw new \DomainException("cannot have negative bytes");
        }

        return new static($bytes);
    }

    /** Display for humans by converting to string. */
    public function __toString()
    {
        [$number, $power] = $this->scaledValueAndPower();
        $units = $this->getUnits($power);
        return sprintf($this->formatString, round($number, $this->maxDecimalPlaces), $units);
    }

    /** You can also get the "raw" scaled value and its log-base-1024 power. */
    public function scaledValueAndPower(): array
    {
        if ($this->bytes == 0) {
            return [0, 0];
        }

        $power = floor(log($this->bytes, $this->logBase));
        $value = $this->bytes / pow($this->logBase, $power);
        return [$value, $power];
    }

    /** For fluent setting of public properties. */
    public function tap(\Closure $callback): self
    {
        $callback($this);
        return $this;
    }

    protected int $bytes;

    protected function __construct(int $bytes)
    {
        $this->bytes = $bytes;
    }

    protected function getUnits($power): string
    {
        if ($power >= count($this->unitScale)) {
            throw new \DomainException("cannot format bytes, too many bytes!");
        }

        return $this->unitScale[$power];
    }
}

Full usage example:

$binaryPrefixBytes = BytesForHumans::fromBytes(5945766364)
    ->tap(function($b) {
        $b->formatString = BytesForHumans::SPACED_FORMAT;
        $b->maxDecimalPlaces = 1;
    });

PHPUnit\Framework\Assert::assertEquals('5.5 GiB', (string)$binaryPrefixBytes);

$decimalPrefixBytes = BytesForHumans::fromBytes(5945766364)
    ->tap(function($b) {
        $b->unitScale = BytesForHumans::FAMILIAR_UNIT_SCALE;
        $b->logBase = 1000;
    });

PHPUnit\Framework\Assert::assertEquals('5.95GB', (string)$decimalPrefixBytes);

@Be1zebub
Copy link

Be1zebub commented Jun 1, 2022

how to revert it?
i want to convert human file-size to int bytes

@buldezir
Copy link

buldezir commented Jun 12, 2022

@Be1zebub that is for wget progress format, but the concept is same

function transferedToBytes(string $transfered): int
{
    $mods = ['B' => 1, 'K' => 1024, 'M' => 1024 ** 2, 'G' => 1024 ** 3];
    $transfered = str_replace(',', '.', $transfered);
    $mod = $transfered[strlen($transfered) - 1];
    return isset($mods[$mod]) ? (int)round(((float)$transfered) * $mods[$mod]) : 0;
}

@jdevinemt
Copy link

Even better

function MakeReadable($bytes) {
    $i = floor(log($bytes, 1024));
    return round($bytes / pow(1024, $i), [0,0,2,2,3][$i]).['B','kB','MB','GB','TB'][$i];
}

I modified @MrCaspan's method to address a few issues.

  • It returns 0B instead of NANB when attempting to format 0 bytes, which was caused by a value of -INF calculated for the factor.
  • I added petabytes, as I think this is the most reasonable max unit to display in most cases. If someone wanted a lower or higher ceiling, the units can easily be modified.
  • When the factor was higher than the highest defined units, the function would just return an integer with no unit indication. This modified version just uses the highest defined unit when the factor exceeds the defined bounds.

It's a longer version for sure, but it's more readable and I think addressing the issues above warrants a lengthier function.

function readableBytes(int $bytes): string
{
    $unitDecimalsByFactor = [
        ['B', 0],
        ['kB', 0],
        ['MB', 2],
        ['GB', 2],
        ['TB', 3],
        ['PB', 3]
    ];

    $factor = $bytes ? floor(log($bytes, 1024)) : 0;
    $factor = min($factor, count($unitDecimalsByFactor) - 1);

    $value = round($bytes / pow(1024, $factor), $unitDecimalsByFactor[$factor][1]);
    $units = $unitDecimalsByFactor[$factor][0];

    return $value.$units;
}

If this function were to be adapted for use with larger factors, a type other than int would be required for the $bytes parameter due to its maximum value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment