 .NET 1.1Vista-Style File Size Formatter
When copying files, Microsoft Windows Vista shows a dialog box that indicates the size of the remaining items to be processed and the copying speed. The values are formatted with three significant figures. This article explains how to recreate this style.
Algorithm Goals
Modern software is capable of producing sets of data that are very large in size. This can lead to long processing times for in-memory or on-disk operations. To improve the perceived performance of such large operations, you may show progress indicators within your application.
Microsoft Vista uses several progress indicators when moving or copying files. A dialog box is displayed that shows a progress bar, the number of items being copied and the estimated time remaining before the operation is complete. The dialog box also provides an expansion button that, when clicked, makes the dialog box grow larger and show even more progress information, including the number and total size of the files remaining and the speed at which the files are being processed.
The image below shows the dialog box. One interesting element is the formatting that is applied to the size of the items remaining. As the numbers as very large, it would be difficult to comprehend these values as simple numbers of bytes. Instead, the values are formatted to show three significant figures and a scale, such as bytes, KB, MB, to give the value context. In this example, the value of 9.63GB is much clearer to the user than 10,345,432,905 bytes.

This article describes how the numeric formatting used by Vista for file operations can be recreated using C#.
The Algorithm and the Code
Depending upon the required use of this formatting function, you may want to create it in a new class, incorporate it in an existing class or simply add the code to a method or event. This article assumes you have a class of some kind prepared and are adding a new method to that class. The code declares a new method with a single parameter to accept the unsigned 64-bit integer to be formatted. The method returns the formatted value as a string.
To begin, create or identify the class that you will add the method to, then add the following method signature:
// Formats a file size
public string FormatFileSize(ulong bytes)
{
}
NB: If you are adding this method to a utility class you may wish to mark it as static.
File Size Scales
The formatted file size that will be created by the FormatFileSize method will include a scale value, such as bytes, kilobytes or megabytes. This scale will provide the meaning of the number once it has been reduced to three significant figures. As we are working with a 64-bit input value, seven scales will be required:
| Scale | Represents |
|---|
| byte | 1 byte | | kilobyte | 1024 bytes | | megabyte | 1024 kilobytes | | gigabyte | 1024 megabytes | | terabyte | 1024 gigabytes | | petabyte | 1024 terabytes | | exabyte | 1024 petabytes |
To hold each of the scales, we can use an enumeration. Each increase in scale will equate to an increase in the enumeration constant value. To create the enumeration, add the following code to the class. In this case, the constant values are plural names. This will make it simpler to add the name to the final formatted string.
// Available file size scales
enum SizeScales
{
bytes,
kilobytes,
megabytes,
gigabytes,
terabytes,
petabytes,
exabytes
}
Detecting a Single Byte
When processing the number of bytes passed to the FormatFileSize method, the result will almost always be formatted as a plural value. However, if the parameter value is one, the return value should be the singular "1 byte". To ensure that this is correct, we can add a simple if statement that detects this special case and immediately returns the correct value. To do so, add the following line to the FormatFileSize method.
if (bytes == 1) return "1 byte";
NB: It is not necessary to perform this check for any other size scale as large numbers will always be returned with decimal places. As these values may also be rounded, it is more appropriate to use a plural scale, as in the example "1.00 kilobytes".
Scaling the Value
The second step of the process is to scale the number until it can be expressed with three significant digits. This is achieved by repeatedly dividing by 1,024 until the value is less than one thousand. This division occurs within a while loop that also increments a scale value during each iteration.
To add the loop, add the following code to the FormatFileSize method:
decimal filesize = bytes;
SizeScales scale = 0;
while (Math.Round(filesize) >= 1000)
{
filesize /= 1024;
scale++;
}
As you can see, the file size is copied into a decimal value. This will allow fractional values to be expressed. The scale value starts at zero, indicating that the file size is initially held in bytes. After the first iteration of the loop, should there be one, the filesize variable will be reduced by a factor of 1,024 whilst the scale will be increased to one, indicating a value in kilobytes.
The rounding in the loop definition checks that the file size is greater than or equal to one thousand after rounding. If, for example, the file size were 999.9, the loop would continue. The rounding is essential, as later in the algorithm the value will be formatted to achieve three significant digits. We do not want this formatting to round the value upwards to one thousand, which would use four significant digits.
Formatting the Scaled Value
On completion of the loop, we will have a file size that is between zero and 999 after rounding. This value can now be included in a string along with the scale size constant name. Firstly, the value must be rounded to three significant figures. This can be combined with the conversion to a string using the ToString method and a format specifier. In this case, the "f" format specifier will be used to indicate a fixed number of decimal places.
The number of decimal places to use depends upon the file size value. If the value is one hundred or greater, no decimal places will be included. For smaller values than this that are ten or larger, one decimal place is required. Finally, for values below ten, two decimal places will be included in the format specifier. The exception to this rule is when the scale is set to bytes. In this case, we do not want to show bytes with decimal places so the number is formatted without any.
The code uses several if statements to determine the required formatting before adding the enumeration value using string.Format. The resultant string is then returned. To complete the method, add the following code:
string formatted;
if (filesize >= 100 || scale == SizeScales.bytes)
formatted = filesize.ToString("f0");
else if (filesize >= 10)
formatted = filesize.ToString("f1");
else
formatted = filesize.ToString("f2");
return string.Format("{0} {1}", formatted, scale);
Testing the Code
You can now test the method by calling it, passing an unsigned integer value, and reviewing the results. For a Windows forms-based demonstration of the method, download the source code and demo using the link at the top of this article.
|