Sun
Mar 25
2007

Formatting File Sizes

In Windows Explorer file sizes are displayed in two different formats. In the Properties page, file sizes are displayed in bytes, KB, MB, GB or TB depending on the size. The StrFormatByteSize() Win32 API can reproduce this format. The second format is used under the “Size” column when viewing a folder. File sizes are displayed in whole KB with thousands separators. I have not found a API call to reproduce this format. The StrFormatKBSize() function is close but differs (it displays two decimal places).

Here are two pure C# functions to reproduce these formats:

/// <summary>
/// Converts a number value into a string that represents the number 
/// expressed in whole kilobytes. This is a format similar to the 
///	Windows Explorer "Size" column.
/// </summary>
public static string FileSizeToStringKB(long fileSize)
{
	return string.Format("{0:n0} KB", Math.Ceiling((double)fileSize / 1024));
}
 
 
/// <summary>
/// Converts a numeric value into a string that represents the number 
///	expressed as a size value in bytes, kilobytes, megabytes, gigabytes, 
///	or terabytes depending on the size. Output is identical to 
///	StrFormatByteSize() in shlwapi.dll. This is a format similar to 
/// the Windows Explorer file Properties page. For example:
///	     532 ->  532 bytes
///     1240 -> 1.21 KB
///	  235606 ->  230 KB
///  5400016 -> 5.14 MB
/// </summary>
/// <remarks>
///	It was surprisingly difficult to emulate the StrFormatByteSize() function
/// due to a few quirks. First, the function only displays three digits:
///  - displays 2 decimal places for values under 10	(e.g. 2.12 KB)
///  - displays 1 decimal place for values under 100	(e.g. 88.2 KB)
///	 - displays 0 decimal places for values under 1000	(e.g. 532 KB)
///	 - jumps to the next unit of measure for values over 1000  (e.g. 0.97 MB)
/// The second quirk: insiginificant digits are truncated rather than 
/// rounded. The original function likely uses integer math.
/// This implementation was tested to 100 TB.
/// </remarks>
public static string FileSizeToString(long fileSize)
{
	double value;
	string unit;
 
	if (fileSize < 1024)
	{
		return string.Format("{0} bytes", fileSize);
	}
	else 
	{
		value = fileSize;
		value = value / 1024;
		unit = "KB";
		if (value >= 1000)
		{
			value = Math.Floor( value );
			value = value / 1024;
			unit = "MB";
		}
		if (value >= 1000)
		{
			value = Math.Floor( value );
			value = value / 1024;
			unit = "GB";
		}
		if (value >= 1000)
		{			value = Math.Floor( value );
			value = value / 1024;
			unit = "TB";
		}
 
		if (value < 10)
		{
			value = Math.Floor( value * 100 ) / 100;
			return string.Format("{0:f2} {1}", value, unit);
		}
		else if (value < 100)
		{
			value = Math.Floor( value * 10) / 10;
			return string.Format("{0:f1} {1}", value, unit);
		}
		else
		{
			value = Math.Floor( value * 1) / 1;
			return string.Format("{0:f0} {1}", value, unit);
		}
	}
}

1 Comment so far

  1. Greg May 30th, 2008 1:05 pm

    This is really good. Thank you!
    I noticed PB/EB are not handled as in StrFormatByteSize(). I’ve modified it a bit, and will attempt to paste it below:

    public static string FileSizeToString(long fileSize)
    {
    string[] units = { “bytes”, “KB”, “MB”, “GB”, “TB”, “PB”, “EB” };
    string[] formats = { “{0:f0} {1}”, “{0:f1} {1}”, “{0:f2} {1}” };

    double value = fileSize;

    int thousand = 1000;
    int kilobyte = 1024;

    int index = 0;
    int decimals = 0;

    if (value >= kilobyte)
    {
    while (value >= thousand)
    {
    value = Math.Floor(value) / kilobyte;
    index++;
    }

    if (value < 10)
    {
    value = Math.Floor(value * 100) / 100;
    decimals = 2;
    }
    else if (value < 100)
    {
    value = Math.Floor(value * 10) / 10;
    decimals = 1;
    }
    else
    {
    value = Math.Floor(value * 1) / 1;
    // precision = 0; // already zero
    }
    }

    return String.Format(formats[decimals], value, units[index]);
    }

Leave a reply

© 2009 Brian Low. All rights reserved.