leepowers
leepowers

Reputation: 38308

Format bytes to kilobytes, megabytes, gigabytes

Scenario: the size of various files are stored in a database as bytes. What's the best way to format this size info to kilobytes, megabytes and gigabytes? For instance I have an MP3 that Ubuntu displays as "5.2 MB (5445632 bytes)". How would I display this on a web page as "5.2 MB" AND have files less than one megabyte display as KB and files one gigabyte and above display as GB?

Upvotes: 231

Views: 267850

Answers (30)

Rodrigo Prazim
Rodrigo Prazim

Reputation: 858

To convert a number of bytes into a human-readable string with the appropriate unit (B, KB, MB, GB, TB), you can use the following function. It works by calculating the appropriate unit based on the size of the input and then formatting the number to the desired precision.

function formatBytes(size, precision = 2) { 
    if (size === 0) return "0B";
    const base = Math.floor(Math.log(size) / Math.log(1024));
    const suffixes = ['B', 'KB', 'MB', 'GB', 'TB'];

    return (size / Math.pow(1024, base)).toFixed(precision) + suffixes[base];
}

Explanation:

  • Logarithmic calculation: We use the logarithm (Math.log(size) / Math.log(1024)) to find out the base (KB, MB, etc.) corresponding to the byte size.
  • Suffixes: The array suffixes contains the unit labels (B, KB, MB, GB, TB).
  • Precision: You can control the number of decimal places with the precision parameter, which defaults to 2.

Example:

console.log(formatBytes(1024)); // "1.00 KB"
console.log(formatBytes(1048576)); // "1.00 MB"
console.log(formatBytes(123456789)); // "117.74 MB"

This function should correctly convert byte sizes into a human-readable format.

Upvotes: 0

Gras Double
Gras Double

Reputation: 16373

This is yet another answer, but I thought some of the posted codes were too much complicated, so here is the implementation I wrote:

function display_size($bytes, $precision = 2) {
    static $units = [
        'TiB' => 1024 ** 4, // requires 64-bit build, otherwise comment out this line
        'GiB' => 1024 ** 3,
        'MiB' => 1024 ** 2,
        'KiB' => 1024
    ];

    foreach ($units as $unit => $value) {
        if ($bytes > $value) {
            return round($bytes / $value, $precision) . ' ' . $unit;
        }
    }

    return $bytes . ' B';
}

A few notes (so that this answer is not "missing its educational explanation"):

  • Rather than the pow() function, I'm using the exponentiation operator (available since PHP 5.6).
  • I'm storing the $units array in a static variable (a technique called "memoization"), so it is generated only one time (at the first call of the function), instead of at every call of the function. (I benchmarked and yes, there is a small performance gain.)
  • About the units based on powers of 10 (e.g. MB) vs based on powers of 2 (e.g. MiB), you may read Byte#Multiple-byte units on Wikipedia.
  • You need a 64-bit build of PHP, otherwise the TiB unit (1,099,511,627,776) would exceed the largest integer supported (2,147,483,647 on 32-bit builds). Also, $bytes would exceed that value with filesizes larger than 2 GB.

Upvotes: 1

Leo
Leo

Reputation: 38180

function formatBytes($bytes, $precision = 2) { 
    $units = array('B', 'KB', 'MB', 'GB', 'TB'); 
   
    $bytes = max($bytes, 0); 
    $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); 
    $pow = min($pow, count($units) - 1); 
   
    // Uncomment one of the following alternatives
    // $bytes /= pow(1024, $pow);
    // $bytes /= (1 << (10 * $pow)); 
   
    return round($bytes, $precision) . $units[$pow]; 
} 

(Taken from php.net, there are many other examples there, but I like this one best :-)

Upvotes: 381

Udochukwu Enwerem
Udochukwu Enwerem

Reputation: 2823

My own implementation for getting formatted file size from integer size. Simple to understand and easy to extend to accommodate larger files - Just follow the pattern.

<?php

function getFormattedFileSize($size, $precision) 
{
    switch (true) 
    {
        case ($size/1024 < 1):
            return $size.'B';
        case ($size/pow(1024, 2) < 1):
            return round($size/1024, $precision).'KB';
        case ($size/pow(1024, 3) < 1):
            return round($size/pow(1024, 2), $precision).'MB';
        case ($size/pow(1024, 4) < 1):
            return round($size/pow(1024, 3), $precision).'GB';
        case ($size/pow(1024, 5) < 1):
            return round($size/pow(1024, 4), $precision).'TB';
        default:
            return 'Error: invalid input or file is too large.';
    }
}

Upvotes: 3

Zombo
Zombo

Reputation: 1

Here is an option using log10:

<?php

function format_number(float $d): string {
   $e = (int)(log10($d) / 3);
   return sprintf('%.3f', $d / 1e3 ** $e) . ['', ' k', ' M', ' G'][$e];
}

$s = format_number(9012345678);
var_dump($s == '9.012 G');

https://php.net/function.log10

Upvotes: 2

Pavel Tzonkov
Pavel Tzonkov

Reputation: 272

Flexible solution:

function size($size, array $options=null) {

    $o = [
        'binary' => false,
        'decimalPlaces' => 2,
        'decimalSeparator' => '.',
        'thausandsSeparator' => '',
        'maxThreshold' => false, // or thresholds key
        'suffix' => [
            'thresholds' => ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'],
            'decimal' => ' {threshold}B',
            'binary' => ' {threshold}iB',
            'bytes' => ' B'
        ]
    ];

    if ($options !== null)
        $o = array_replace_recursive($o, $options);

    $base = $o['binary'] ? 1024 : 1000;
    $exp = $size ? floor(log($size) / log($base)) : 0;

    if (($o['maxThreshold'] !== false) &&
        ($o['maxThreshold'] < $exp)
    )
        $exp = $o['maxThreshold'];

    return !$exp
        ? (round($size) . $o['suffix']['bytes'])
        : (
            number_format(
                $size / pow($base, $exp),
                $o['decimalPlaces'],
                $o['decimalSeparator'],
                $o['thausandsSeparator']
            ) .
            str_replace(
                '{threshold}',
                $o['suffix']['thresholds'][$exp],
                $o['suffix'][$o['binary'] ? 'binary' : 'decimal']
            )
        );
}

var_dump(size(disk_free_space('/')));
// string(8) "14.63 GB"
var_dump(size(disk_free_space('/'), ['binary' => true]));
// string(9) "13.63 GiB"
var_dump(size(disk_free_space('/'), ['maxThreshold' => 2]));
// string(11) "14631.90 MB"
var_dump(size(disk_free_space('/'), ['binary' => true, 'maxThreshold' => 2]));
// string(12) "13954.07 MiB"

Upvotes: 3

Gabriel Barcellos
Gabriel Barcellos

Reputation: 19

I did this converting all input to byte and so converting to any output needed. Also, I used a auxiliar function to get base 1000 or 1024, but left it flex to decide use 1024 on popular type (without 'i', like MB instead of MiB).

    public function converte_binario($size=0,$format_in='B',$format_out='MB',$force_in_1024=false,$force_out_1024=false,$precisao=5,$return_format=true,$decimal=',',$centena=''){
    $out = false;

    if( (is_numeric($size)) && ($size>0)){
        $in_data = $this->converte_binario_aux($format_in,$force_in_1024);
        $out_data = $this->converte_binario_aux($format_out,$force_out_1024);

        // se formato de entrada e saída foram encontrados
        if( ((isset($in_data['sucesso'])) && ($in_data['sucesso']==true)) && ((isset($out_data['sucesso'])) && ($out_data['sucesso']==true))){
            // converte formato de entrada para bytes.
            $size_bytes_in = $size * (pow($in_data['base'], $in_data['pot']));
            $size_byte_out = (pow($out_data['base'], $out_data['pot']));
            // transforma bytes na unidade de destino
            $out = number_format($size_bytes_in / $size_byte_out,$precisao,$decimal,$centena);
            if($return_format){
                $out .= $format_out;
            }
        }
    }
    return $out;
}

public function converte_binario_aux($format=false,$force_1024=false){
    $out = [];
    $out['sucesso'] = false;
    $out['base'] = 0;
    $out['pot'] = 0;
    if((is_string($format) && (strlen($format)>0))){
        $format = trim(strtolower($format));
        $units_1000 = ['b','kb' ,'mb' ,'gb' ,'tb' ,'pb' ,'eb' ,'zb' ,'yb' ];
        $units_1024 = ['b','kib','mib','gib','tib','pib','eib','zib','yib'];
        $pot = array_search($format,$units_1000);
        if( (is_numeric($pot)) && ($pot>=0)){
            $out['pot'] = $pot;
            $out['base'] = 1000;
            $out['sucesso'] = true;
        }
        else{
            $pot = array_search($format,$units_1024);
            if( (is_numeric($pot)) && ($pot>=0)){
                $out['pot'] = $pot;
                $out['base'] = 1024;
                $out['sucesso'] = true;
            }
        }
        if($force_1024){
            $out['base'] = 1024;
        }
    }
    return $out;
}

Upvotes: 1

Ben Johnson
Ben Johnson

Reputation: 2617

Albeit a bit stale, this library offers a tested and robust conversion API:

https://github.com/gabrielelana/byte-units

Once installed:

\ByteUnits\Binary::bytes(1024)->format();

// Output: "1.00KiB"

And to convert in the other direction:

\ByteUnits\Binary::parse('1KiB')->numberOfBytes();

// Output: "1024"

Beyond basic conversion, it offers methods for addition, subtraction, comparison, etc.

I am no way affiliated with this library.

Upvotes: 2

Steely Wing
Steely Wing

Reputation: 17597

Base on Leo's answer, add

  • Support for negative
  • Support 0 < value < 1 ( Ex: 0.2, will cause log(value) = negative number )

If you want max unit to Mega, change to $units = explode(' ', ' K M');


function formatUnit($value, $precision = 2) {
    $units = explode(' ', ' K M G T P E Z Y');

    if ($value < 0) {
        return '-' . formatUnit(abs($value));
    }

    if ($value < 1) {
        return $value . $units[0];
    }

    $power = min(
        floor(log($value, 1024)),
        count($units) - 1
    );

    return round($value / pow(1024, $power), $precision) . $units[$power];
}

Upvotes: 0

Madushanka Sampath
Madushanka Sampath

Reputation: 169

function convertToReadableSize($size)
{
  $base = log($size) / log(1024);
  $suffix = array("B", "KB", "MB", "GB", "TB");
  $f_base = floor($base);
  return round(pow(1024, $base - floor($base)), 1) . $suffix[$f_base];
}

Just call the function

echo convertToReadableSize(1024); // Outputs '1KB'
echo convertToReadableSize(1024 * 1024); // Outputs '1MB'

Upvotes: 4

Juan Lago
Juan Lago

Reputation: 1048

I developed my own function that convert human readable memory size to different sizes.

function convertMemorySize($strval, string $to_unit = 'b')
{
    $strval    = strtolower(str_replace(' ', '', $strval));
    $val       = floatval($strval);
    $to_unit   = strtolower(trim($to_unit))[0];
    $from_unit = str_replace($val, '', $strval);
    $from_unit = empty($from_unit) ? 'b' : trim($from_unit)[0];
    $units     = 'kmgtph';  // (k)ilobyte, (m)egabyte, (g)igabyte and so on...


    // Convert to bytes
    if ($from_unit !== 'b')
        $val *= 1024 ** (strpos($units, $from_unit) + 1);


    // Convert to unit
    if ($to_unit !== 'b')
        $val /= 1024 ** (strpos($units, $to_unit) + 1);


    return $val;
}


convertMemorySize('1024Kb', 'Mb');  // 1
convertMemorySize('1024', 'k')      // 1
convertMemorySize('5.2Mb', 'b')     // 5452595.2
convertMemorySize('10 kilobytes', 'bytes') // 10240
convertMemorySize(2048, 'k')        // By default convert from bytes, result is 2

This function accepts any memory size abbreviation like "Megabyte, MB, Mb, mb, m, kilobyte, K, KB, b, Terabyte, T...." so it is typo safe.

Upvotes: -1

John Erck
John Erck

Reputation: 9528

Extremely simple function to get human file size.

Original source: http://php.net/manual/de/function.filesize.php#106569

Copy/paste code:

<?php
function human_filesize($bytes, $decimals = 2) {
  $sz = 'BKMGTP';
  $factor = floor((strlen($bytes) - 1) / 3);
  return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
}
?>

Upvotes: 4

EML
EML

Reputation: 1

I figured I would add a meshing of two submitters code (Using John Himmelman's code, which is in this thread, and using Eugene Kuzmenko's code) that I'm using.

function swissConverter($value, $format = true, $precision = 2) {
    //Below converts value into bytes depending on input (specify mb, for 
    //example)
    $bytes = preg_replace_callback('/^\s*(\d+)\s*(?:([kmgt]?)b?)?\s*$/i', 
    function ($m) {
        switch (strtolower($m[2])) {
          case 't': $m[1] *= 1024;
          case 'g': $m[1] *= 1024;
          case 'm': $m[1] *= 1024;
          case 'k': $m[1] *= 1024;
        }
        return $m[1];
        }, $value);
    if(is_numeric($bytes)) {
        if($format === true) {
            //Below converts bytes into proper formatting (human readable 
            //basically)
            $base = log($bytes, 1024);
            $suffixes = array('', 'KB', 'MB', 'GB', 'TB');   

            return round(pow(1024, $base - floor($base)), $precision) .' '. 
                     $suffixes[floor($base)];
        } else {
            return $bytes;
        }
    } else {
        return NULL; //Change to prefered response
    }
}

This uses Eugene's code to format the $value into bytes (I keep my data in MB, so it converts my data: 10485760 MB into 10995116277760) - it then uses John's code to convert it into the proper display value (10995116277760 into 10 TB).

I've found this really helpful - so my thanks to the two submitters!

Upvotes: -1

SebHallin
SebHallin

Reputation: 891

Simple function

function formatBytes($size, $precision = 0){
    $unit = ['Byte','KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];

    for($i = 0; $size >= 1024 && $i < count($unit)-1; $i++){
        $size /= 1024;
    }

    return round($size, $precision).' '.$unit[$i];
}

echo formatBytes('1876144', 2);
//returns 1.79 MiB

Upvotes: 6

ZettiCaletti
ZettiCaletti

Reputation: 29

I don't know why you should make it so complicated as the others.

The following code is much simpler to understand and about 25% faster than the other solutions who uses the log function (called the function 20 Mio. times with different parameters)

function formatBytes($bytes, $precision = 2) {
    $units = ['Byte', 'Kilobyte', 'Megabyte', 'Gigabyte', 'Terabyte'];
    $i = 0;

    while($bytes > 1024) {
        $bytes /= 1024;
        $i++;
    }
    return round($bytes, $precision) . ' ' . $units[$i];
}

Upvotes: 2

Christian
Christian

Reputation: 612

Another condensed implementation which can translate to the base 1024 (binary) or base 1000 (decimal) and also works with incredibly large numbers hence of the use of the bc library:

function renderSize($byte,$precision=2,$mibi=true)
{
    $base = (string)($mibi?1024:1000);
    $labels = array('K','M','G','T','P','E','Z','Y');
    for($i=8;$i>=1;$i--)
        if(bccomp($byte,bcpow($base, $i))>=0)
            return bcdiv($byte,bcpow($base, $i), $precision).' '.$labels[$i-1].($mibi?'iB':'B');
    return $byte.' Byte';
}

Upvotes: -1

David B&#233;langer
David B&#233;langer

Reputation: 7438

I know it's maybe a little late to answer this question but, more data is not going to kill someone. Here's a very fast function :

function format_filesize($B, $D=2){
    $S = 'BkMGTPEZY';
    $F = floor((strlen($B) - 1) / 3);
    return sprintf("%.{$D}f", $B/pow(1024, $F)).' '.@$S[$F].'B';
}

EDIT: I updated my post to include the fix proposed by camomileCase:

function format_filesize($B, $D=2){
    $S = 'kMGTPEZY';
    $F = floor((strlen($B) - 1) / 3);
    return sprintf("%.{$D}f", $B/pow(1024, $F)).' '.@$S[$F-1].'B';
}

Upvotes: 5

John Himmelman
John Himmelman

Reputation: 22000

This is Chris Jester-Young's implementation, cleanest I've ever seen, combined with php.net's and a precision argument.

function formatBytes($size, $precision = 2)
{
    $base = log($size, 1024);
    $suffixes = array('', 'K', 'M', 'G', 'T');   

    return round(pow(1024, $base - floor($base)), $precision) .' '. $suffixes[floor($base)];
}

echo formatBytes(24962496);
// 23.81M

echo formatBytes(24962496, 0);
// 24M

echo formatBytes(24962496, 4);
// 23.8061M

Upvotes: 248

user3690595
user3690595

Reputation: 41

It's a little late but a slightly faster version of the accepted answer is below:

function formatBytes($bytes, $precision)
{
    $unit_list = array
    (
        'B',
        'KB',
        'MB',
        'GB',
        'TB',
    );

    $bytes = max($bytes, 0);
    $index = floor(log($bytes, 2) / 10);
    $index = min($index, count($unit_list) - 1);
    $bytes /= pow(1024, $index);

    return round($bytes, $precision) . ' ' . $unit_list[$index];
}

It's more efficient, due to performing a single log-2 operation instead of two log-e operations.

It's actually faster to do the more obvious solution below, however:

function formatBytes($bytes, $precision)
{
    $unit_list = array
    (
        'B',
        'KB',
        'MB',
        'GB',
        'TB',
    );

    $index_max = count($unit_list) - 1;
    $bytes = max($bytes, 0);

    for ($index = 0; $bytes >= 1024 && $index < $index_max; $index++)
    {
        $bytes /= 1024;
    }

    return round($bytes, $precision) . ' ' . $unit_list[$index];
}

This is because as the index is calculated at the same time as the value of the number of bytes in the appropriate unit. This cut the execution time by about 35% (a 55% speed increase).

Upvotes: -1

Kerem &#199;akır
Kerem &#199;akır

Reputation: 401

function changeType($size, $type, $end){
    $arr = ['B', 'KB', 'MB', 'GB', 'TB'];
    $tSayi = array_search($type, $arr);
    $eSayi = array_search($end, $arr);
    $pow = $eSayi - $tSayi;
    return $size * pow(1024 * $pow) . ' ' . $end;
}

echo changeType(500, 'B', 'KB');

Upvotes: 1

ya.teck
ya.teck

Reputation: 2336

Here is simplified implementation of the Drupal format_size function:

/**
 * Generates a string representation for the given byte count.
 *
 * @param $size
 *   A size in bytes.
 *
 * @return
 *   A string representation of the size.
 */
function format_size($size) {
  if ($size < 1024) {
    return $size . ' B';
  }
  else {
    $size = $size / 1024;
    $units = ['KB', 'MB', 'GB', 'TB'];
    foreach ($units as $unit) {
      if (round($size, 2) >= 1024) {
        $size = $size / 1024;
      }
      else {
        break;
      }
    }
    return round($size, 2) . ' ' . $unit;
  }
}

Upvotes: 0

mooky
mooky

Reputation: 11

function byte_format($size) {
    $bytes = array( ' KB', ' MB', ' GB', ' TB' );
    foreach ($bytes as $val) {
        if (1024 <= $size) {
            $size = $size / 1024;
            continue;
        }
        break;
    }
    return round( $size, 1 ) . $val;
}

Upvotes: 0

user24087
user24087

Reputation: 29

My approach

    function file_format_size($bytes, $decimals = 2) {
  $unit_list = array('B', 'KB', 'MB', 'GB', 'PB');

  if ($bytes == 0) {
    return $bytes . ' ' . $unit_list[0];
  }

  $unit_count = count($unit_list);
  for ($i = $unit_count - 1; $i >= 0; $i--) {
    $power = $i * 10;
    if (($bytes >> $power) >= 1)
      return round($bytes / (1 << $power), $decimals) . ' ' . $unit_list[$i];
  }
}

Upvotes: 2

guari
guari

Reputation: 3820

Just my alternative, short and clean:

/**
 * @param int $bytes Number of bytes (eg. 25907)
 * @param int $precision [optional] Number of digits after the decimal point (eg. 1)
 * @return string Value converted with unit (eg. 25.3KB)
 */
function formatBytes($bytes, $precision = 2) {
    $unit = ["B", "KB", "MB", "GB"];
    $exp = floor(log($bytes, 1024)) | 0;
    return round($bytes / (pow(1024, $exp)), $precision).$unit[$exp];
}

or, more stupid and efficent:

function formatBytes($bytes, $precision = 2) {
    if ($bytes > pow(1024,3)) return round($bytes / pow(1024,3), $precision)."GB";
    else if ($bytes > pow(1024,2)) return round($bytes / pow(1024,2), $precision)."MB";
    else if ($bytes > 1024) return round($bytes / 1024, $precision)."KB";
    else return ($bytes)."B";
}

Upvotes: 9

Yahia Mgarrech
Yahia Mgarrech

Reputation: 682

try this ;)

function bytesToSize($bytes) {
                $sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
                if ($bytes == 0) return 'n/a';
                $i = intval(floor(log($bytes) / log(1024)));
                if ($i == 0) return $bytes . ' ' . $sizes[$i]; 
                return round(($bytes / pow(1024, $i)),1,PHP_ROUND_HALF_UP). ' ' . $sizes[$i];
            }
echo bytesToSize(10000050300);

Upvotes: 1

Janith Chinthana
Janith Chinthana

Reputation: 3844

I succeeded with following function,

    function format_size($size) {
        $mod = 1024;
        $units = explode(' ','B KB MB GB TB PB');
        for ($i = 0; $size > $mod; $i++) {
            $size /= $mod;
        }
        return round($size, 2) . ' ' . $units[$i];
    }

Upvotes: 1

Yanni
Yanni

Reputation: 2583

use this function if you want a short code

bcdiv()

$size = 11485760;
echo bcdiv($size, 1048576, 0); // return: 10

echo bcdiv($size, 1048576, 2); // return: 10,9

echo bcdiv($size, 1048576, 2); // return: 10,95

echo bcdiv($size, 1048576, 3); // return: 10,953

Upvotes: 8

C. K. Young
C. K. Young

Reputation: 223013

Pseudocode:

$base = log($size) / log(1024);
$suffix = array("", "k", "M", "G", "T")[floor($base)];
return pow(1024, $base - floor($base)) . $suffix;

Upvotes: 97

ryeguy
ryeguy

Reputation: 66851

This is Kohana's implementation, you could use it:

public static function bytes($bytes, $force_unit = NULL, $format = NULL, $si = TRUE)
{
    // Format string
    $format = ($format === NULL) ? '%01.2f %s' : (string) $format;

    // IEC prefixes (binary)
    if ($si == FALSE OR strpos($force_unit, 'i') !== FALSE)
    {
        $units = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB');
        $mod   = 1024;
    }
    // SI prefixes (decimal)
    else
    {
        $units = array('B', 'kB', 'MB', 'GB', 'TB', 'PB');
        $mod   = 1000;
    }

    // Determine unit to use
    if (($power = array_search((string) $force_unit, $units)) === FALSE)
    {
        $power = ($bytes > 0) ? floor(log($bytes, $mod)) : 0;
    }

    return sprintf($format, $bytes / pow($mod, $power), $units[$power]);
}

Upvotes: 16

Vonder
Vonder

Reputation: 4061

Just divide it by 1024 for kb, 1024^2 for mb and 1024^3 for GB. As simple as that.

Upvotes: 18

Related Questions