298

How do I get a human-readable file size in bytes abbreviation using .NET?

Example: Take input 7,326,629 and display 6.98 MB

Bart
  • 18,467
  • 7
  • 66
  • 73
Larsenal
  • 45,294
  • 40
  • 140
  • 210
  • What is about http://stackoverflow.com/questions/128618/c-file-size-format-provider ? – Kiquenet Jul 18 '13 at 08:24
  • 1
    And http://stackoverflow.com/questions/14488796/does-net-provide-an-easy-way-convert-bytes-to-kb-mb-gb-etc .... – vapcguy Apr 01 '15 at 03:45

21 Answers21

383

This is not the most efficient way to do it, but it's easier to read if you are not familiar with log maths, and should be fast enough for most scenarios.

string[] sizes = { "B", "KB", "MB", "GB", "TB" };
double len = new FileInfo(filename).Length;
int order = 0;
while (len >= 1024 && order < sizes.Length - 1) {
    order++;
    len = len/1024;
}

// Adjust the format string to your preferences. For example "{0:0.#}{1}" would
// show a single decimal place, and no space.
string result = String.Format("{0:0.##} {1}", len, sizes[order]);
David Thibault
  • 8,120
  • 3
  • 34
  • 50
  • I think "len >= 1024 && ..." will be better, but its a detail. – TcKs Nov 11 '08 at 18:08
  • 1
    This is exactly what I would do... Except I'd use "{0:0.#}{1}" as the format string... There's usually no real need for two digits after the dot and I don't like putting a space there. But that's just me. – configurator Nov 11 '08 at 18:21
  • 14
    I believe you could use Math.Log to determine the order instead of using a while loop. – Francois Botha Nov 28 '10 at 10:52
  • 1
    @Francois Botha, indeed :) http://stackoverflow.com/questions/281640/how-do-i-get-a-human-readable-file-size-using-net/4967106#4967106 – Constantin Feb 11 '11 at 08:52
  • 18
    Also, KB is 1000 bytes. 1024 bytes is [KiB](http://en.wikipedia.org/wiki/Kibibyte). – Constantin Feb 11 '11 at 08:54
  • 19
    @Constantin well that depends on the OS? Windows still counts 1024 bytes as 1 KB and 1 MB = 1024 KB, Personally i wanna throw the KiB out the window and just count every thing using 1024?... – Peter Aug 08 '13 at 22:46
  • 5
    @Petoj it does not depend on the OS, the definition is OS-agnostic. From Wikipedia: `The unit was established by the International Electrotechnical Commission (IEC) in 1998 and has been accepted for use by all major standards organizations` – ANeves thinks SE is evil Nov 18 '13 at 17:53
  • @ANeves correct me if im wrong but if you look at the file size in windows it will use 1024, but if you do the same in a Mac OS x it will instead use 1000? so standard or not how the OS calculates file sizes is not the same. and in both cases it say KB\MB and so on.. – Peter Nov 18 '13 at 18:56
  • Why is loop and just divisions is not an efficient way to solve the problem than having a code with lots of Maths -- Math.Abs, Math.Floor, Math.Log, Converting to integer, Math.Round, Math.Pow, Math.Sign? Wasn't this tons of maths just make a huge spike on the processor? – Jayson Ragasa Dec 09 '13 at 23:36
  • 3
    I prefer this the code as it seems to run faster but I modified it slightly to allow for different numbers of decimal places. Smaller numbers are better showing 2 decimal places, eg 1.38MB whereas larger numbers require fewer decimals eg 246k or 23.5KB: – Myke Black Sep 08 '14 at 11:21
  • 1
    Got a bug in there for TB because order is incremented (++order == sizes.Length) but len is not reduced. –  Dec 28 '16 at 13:48
  • @buffjape True! I've updated the code snippet. Thank you! – David Thibault Dec 30 '16 at 04:44
  • While condition should be `order < sizes.Length-1` because `sizes[sizes.Length]` will crash. –  Jan 03 '17 at 12:34
  • @buffjape You are right. I knew I should have double checked this edit, but I was short on time. Thanks a bunch! – David Thibault Jan 03 '17 at 20:20
  • MB/GB/TB is what hard disk production companies abuse to sell you a 10,000,000,000,000 byte disk as containing "10 TB", while it's really only 9.09 TiB. _That_ is the difference between TB and TiB, and why it is important to use TiB. – Nyerguds Jan 20 '20 at 08:40
333

using Log to solve the problem....

static String BytesToString(long byteCount)
{
    string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; //Longs run out around EB
    if (byteCount == 0)
        return "0" + suf[0];
    long bytes = Math.Abs(byteCount);
    int place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024)));
    double num = Math.Round(bytes / Math.Pow(1024, place), 1);
    return (Math.Sign(byteCount) * num).ToString() + suf[place];
}

Also in c#, but should be a snap to convert. Also I rounded to 1 decimal place for readability.

Basically Determine the number of decimal places in Base 1024 and then divide by 1024^decimalplaces.

And some samples of use and output:

Console.WriteLine(BytesToString(9223372036854775807));  //Results in 8EB
Console.WriteLine(BytesToString(0));                    //Results in 0B
Console.WriteLine(BytesToString(1024));                 //Results in 1KB
Console.WriteLine(BytesToString(2000000));              //Results in 1.9MB
Console.WriteLine(BytesToString(-9023372036854775807)); //Results in -7.8EB

Edit: Was pointed out that I missed a math.floor, so I incorporated it. (Convert.ToInt32 uses rounding, not truncating and that's why Floor is necessary.) Thanks for the catch.

Edit2: There were a couple of comments about negative sizes and 0 byte sizes, so I updated to handle those 2 cases.

Erik Schierboom
  • 15,025
  • 10
  • 60
  • 79
deepee1
  • 11,640
  • 4
  • 28
  • 43
  • 7
    I want to warn that while this answer is indeed a short piece of code it isn't the most optimized. I'd like you to take a look at the method posted by @humbads. I ran microtesting sending 10 000 000 randomly generated filesizes through both methods and this brings up numbers that his method is ~30% faster. I did some further cleaning of his method however (unnesecary assignments & casting). Furthermore I ran a test with a negative size (when you're comparing files) while the method of humbads flawlessly processes this this Log method will throw an exception! – IvanL Aug 10 '12 at 07:56
  • 1
    Yep, you should add Math.Abs for negative sizes. Furthermore the code does not handle the case if the size is exactly 0. – dasheddot Jan 04 '13 at 19:45
  • Math.Abs, Math.Floor, Math.Log, Converting to integer, Math.Round, Math.Pow, Math.Sign, Adding, Multiplying, Dividing? Wasn't this tons of maths just make a huge spike on the processor. This is probably slower than @humbads code – Jayson Ragasa Dec 09 '13 at 23:55
  • Fails for `double.MaxValue` (place = 102) – BrunoLM Dec 27 '13 at 16:56
  • Works great! To mimic the way windows works (at least on my Windows 7 ultimate), replace the Math.Round with Math.Ceiling. Thanks again. I like this solution. – Djibril NDIAYE Jul 28 '14 at 04:26
115

A tested and significantly optimized version of the requested function is posted here:

C# Human Readable File Size - Optimized Function

Source code:

// Returns the human-readable file size for an arbitrary, 64-bit file size 
// The default format is "0.### XB", e.g. "4.2 KB" or "1.434 GB"
public string GetBytesReadable(long i)
{
    // Get absolute value
    long absolute_i = (i < 0 ? -i : i);
    // Determine the suffix and readable value
    string suffix;
    double readable;
    if (absolute_i >= 0x1000000000000000) // Exabyte
    {
        suffix = "EB";
        readable = (i >> 50);
    }
    else if (absolute_i >= 0x4000000000000) // Petabyte
    {
        suffix = "PB";
        readable = (i >> 40);
    }
    else if (absolute_i >= 0x10000000000) // Terabyte
    {
        suffix = "TB";
        readable = (i >> 30);
    }
    else if (absolute_i >= 0x40000000) // Gigabyte
    {
        suffix = "GB";
        readable = (i >> 20);
    }
    else if (absolute_i >= 0x100000) // Megabyte
    {
        suffix = "MB";
        readable = (i >> 10);
    }
    else if (absolute_i >= 0x400) // Kilobyte
    {
        suffix = "KB";
        readable = i;
    }
    else
    {
        return i.ToString("0 B"); // Byte
    }
    // Divide by 1024 to get fractional value
    readable = (readable / 1024);
    // Return formatted number with suffix
    return readable.ToString("0.### ") + suffix;
}
humbads
  • 2,737
  • 1
  • 22
  • 21
  • 2
    +1! Simpler and straight forward! Makes the processor do the math easily and faster! – Jayson Ragasa Dec 09 '13 at 23:43
  • FYI, you don't use the value in `double readable = (i < 0 ? -i : i);` anywhere so remove it. one more thing , the cast is redaundat – Royi Namir Jan 02 '15 at 16:51
  • I removed the cast, added comments, and fixed an issue with the negative sign. – humbads Jan 03 '15 at 15:50
  • Great answer. Thanks, Why not just use `Math.Abs`? – kspearrin Jun 30 '17 at 19:35
  • 1
    (i < 0 ? -i : i) is approximately 15% faster than Math.Abs. For one million calls, Math.Abs is 0.5 milliseconds slower on my machine -- 3.2 ms vs 3.7 ms. – humbads Aug 02 '17 at 16:02
  • Should be "MiB", "KiB" etc? – JohnC Jul 01 '20 at 23:40
  • @JohnC, it depends on the audience. I would stick with "MB", "GB", etc. for common usage, and change it to "MiB", "GiB", etc. if the audience is very technical, like developers, engineers, etc. – humbads Jul 03 '20 at 00:17
  • Perhaps both with a switch and use 1024 or 1000 as switched; I'm seeing MiB more and more in the "real world" now – JohnC Jul 03 '20 at 18:46
  • Using .NET Framework 4.6.1, `Math.Abs` is 11 sec faster (14s instead of 3s) for 1 billion calls. – Otiel Nov 12 '20 at 14:44
73
[DllImport ( "Shlwapi.dll", CharSet = CharSet.Auto )]
public static extern long StrFormatByteSize ( 
        long fileSize
        , [MarshalAs ( UnmanagedType.LPTStr )] StringBuilder buffer
        , int bufferSize );


/// <summary>
/// Converts a numeric value into a string that represents the number expressed as a size value in bytes, kilobytes, megabytes, or gigabytes, depending on the size.
/// </summary>
/// <param name="filelength">The numeric value to be converted.</param>
/// <returns>the converted string</returns>
public static string StrFormatByteSize (long filesize) {
     StringBuilder sb = new StringBuilder( 11 );
     StrFormatByteSize( filesize, sb, sb.Capacity );
     return sb.ToString();
}

From: http://www.pinvoke.net/default.aspx/shlwapi/StrFormatByteSize.html

Bob
  • 90,304
  • 29
  • 114
  • 125
  • 40
    I might be a noob, but using such gigant cannon as pinvoke for killing that duck is a big misuse. – Bart Apr 29 '11 at 19:46
  • 28
    Is this what explorer uses? If so, then magnificently useful for letting people match the file size you show them with what explorer shows. – Andrew Backer Sep 23 '11 at 09:14
  • 8
    And one that doesn't reinvent the wheel – Matthew Lock Oct 21 '14 at 06:24
  • Isn't 11 characters a constant limit and a bit low for that? I mean, other languages might use more characters for the byte size acronym, or other formatting styles. – Ray Jan 26 '16 at 18:19
  • 1
    @Bart it takes a while for noobs to learn the wisdom in this: "We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil" http://ubiquity.acm.org/article.cfm?id=1513451 – Matthew Lock Apr 18 '17 at 00:22
  • 2
    @Matthew I know this sentence, it is one of my favorites. But the point of my comment was not addressing efficiency but purity. Relaying on PInvoke is last and ultimate weapon in our safe managed world. Why should we bring any risk, that one day this extern will fail or removed, when we have perfectly managed code for this task? Should we test our code relying on this? Will it work on linux? Etc. etc. So many additional questions and I see no potential gain over the answer with highest voting score. – Bart Apr 18 '17 at 08:25
  • 1
    @MatthewLock, Hmm, this is like calling _calculating `20+15` in your head rather than reaching out for the calculator_ reinventing the wheel. – 41686d6564 May 05 '18 at 13:13
  • 2
    This is definitely *not* the way to do it. It might have some use in very specific cases for Windows-only programs if you want to exactly match what the OS displays for sizes; however, with Windows 10 the function uses base 10 rather than base 2 (1 KB = 1000 bytes instead of 1024), so the same code would produce different outputs depending on what version of Windows it is running on. Finally, this is completely useless if you are writing cross-platform code. – Herohtar Jan 01 '20 at 09:33
23

One more way to skin it, without any kind of loops and with negative size support (makes sense for things like file size deltas):

public static class Format
{
    static string[] sizeSuffixes = {
        "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };

    public static string ByteSize(long size)
    {
        Debug.Assert(sizeSuffixes.Length > 0);

        const string formatTemplate = "{0}{1:0.#} {2}";

        if (size == 0)
        {
            return string.Format(formatTemplate, null, 0, sizeSuffixes[0]);
        }

        var absSize = Math.Abs((double)size);
        var fpPower = Math.Log(absSize, 1000);
        var intPower = (int)fpPower;
        var iUnit = intPower >= sizeSuffixes.Length
            ? sizeSuffixes.Length - 1
            : intPower;
        var normSize = absSize / Math.Pow(1000, iUnit);

        return string.Format(
            formatTemplate,
            size < 0 ? "-" : null, normSize, sizeSuffixes[iUnit]);
    }
}

And here is the test suite:

[TestFixture] public class ByteSize
{
    [TestCase(0, Result="0 B")]
    [TestCase(1, Result = "1 B")]
    [TestCase(1000, Result = "1 KB")]
    [TestCase(1500000, Result = "1.5 MB")]
    [TestCase(-1000, Result = "-1 KB")]
    [TestCase(int.MaxValue, Result = "2.1 GB")]
    [TestCase(int.MinValue, Result = "-2.1 GB")]
    [TestCase(long.MaxValue, Result = "9.2 EB")]
    [TestCase(long.MinValue, Result = "-9.2 EB")]
    public string Format_byte_size(long size)
    {
        return Format.ByteSize(size);
    }
}
Constantin
  • 25,664
  • 10
  • 57
  • 79
20

Checkout the ByteSize library. It's the System.TimeSpan for bytes!

It handles the conversion and formatting for you.

var maxFileSize = ByteSize.FromKiloBytes(10);
maxFileSize.Bytes;
maxFileSize.MegaBytes;
maxFileSize.GigaBytes;

It also does string representation and parsing.

// ToString
ByteSize.FromKiloBytes(1024).ToString(); // 1 MB
ByteSize.FromGigabytes(.5).ToString();   // 512 MB
ByteSize.FromGigabytes(1024).ToString(); // 1 TB

// Parsing
ByteSize.Parse("5b");
ByteSize.Parse("1.55B");
Omar
  • 37,391
  • 42
  • 132
  • 207
14

I like to use the following method (it supports up to terabytes, which is enough for most cases, but it can easily be extended):

private string GetSizeString(long length)
{
    long B = 0, KB = 1024, MB = KB * 1024, GB = MB * 1024, TB = GB * 1024;
    double size = length;
    string suffix = nameof(B);

    if (length >= TB) {
        size = Math.Round((double)length / TB, 2);
        suffix = nameof(TB);
    }
    else if (length >= GB) {
        size = Math.Round((double)length / GB, 2);
        suffix = nameof(GB);
    }
    else if (length >= MB) {
        size = Math.Round((double)length / MB, 2);
        suffix = nameof(MB);
    }
    else if (length >= KB) {
        size = Math.Round((double)length / KB, 2);
        suffix = nameof(KB);
    }

    return $"{size} {suffix}";
}

Please keep in mind that this is written for C# 6.0 (2015), so it might need a little editing for earlier versions.

Mark
  • 793
  • 8
  • 18
11
int size = new FileInfo( filePath ).Length / 1024;
string humanKBSize = string.Format( "{0} KB", size );
string humanMBSize = string.Format( "{0} MB", size / 1024 );
string humanGBSize = string.Format( "{0} GB", size / 1024 / 1024 );
TcKs
  • 23,945
  • 9
  • 61
  • 96
  • Good answer. There should be a problem when file size is too small, in which case / 1024 returns 0. You could use a fractional type and call `Math.Ceiling` or something. – nawfal Feb 08 '14 at 21:02
10

Here's a concise answer that determines the unit automatically.

public static string ToBytesCount(this long bytes)
{
    int unit = 1024;
    string unitStr = "b";
    if (bytes < unit) return string.Format("{0} {1}", bytes, unitStr);
    else unitStr = unitStr.ToUpper();
    int exp = (int)(Math.Log(bytes) / Math.Log(unit));
    return string.Format("{0:##.##} {1}{2}", bytes / Math.Pow(unit, exp), "KMGTPEZY"[exp - 1], unitStr);
}

"b" is for bit, "B" is for Byte and "KMGTPEZY" are respectively for kilo, mega, giga, tera, peta, exa, zetta and yotta

One can expand it to take ISO/IEC80000 into account:

public static string ToBytesCount(this long bytes, bool isISO = true)
{
    int unit = 1024;
    string unitStr = "b";
    if (!isISO) unit = 1000;
    if (bytes < unit) return string.Format("{0} {1}", bytes, unitStr);
    else unitStr = unitStr.ToUpper();
    if (isISO) unitStr = "i" + unitStr;
    int exp = (int)(Math.Log(bytes) / Math.Log(unit));
    return string.Format("{0:##.##} {1}{2}", bytes / Math.Pow(unit, exp), "KMGTPEZY"[exp - 1], unitStr);
}
DKH
  • 372
  • 3
  • 12
  • 1
    for everyone wondering why there is an `o` after KMGTPE: Its french (`byte` is `octet` in french). For any other language just replace the `o` with the `b` – Max R. Feb 06 '19 at 10:43
7
string[] suffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
int s = 0;
long size = fileInfo.Length;

while (size >= 1024)
{
    s++;
    size /= 1024;
}

string humanReadable = String.Format("{0} {1}", size, suffixes[s]);
bobwienholt
  • 16,654
  • 3
  • 37
  • 47
  • You should check : while(size >= 1024 && s < suffixes.Length ). – TcKs Nov 11 '08 at 18:08
  • nope... a 64-bit signed integer cannot go beyond the ZB... which represents numbers 2^70. – bobwienholt Nov 11 '08 at 18:12
  • I like this answer best myself, but everybody here put in really in-efficient solutions really, you should use "size = size >> 10" shift is so very much faster than division... and I think that it's good to have the extra greek specifier's there, because in the near future, a posiable DLR function wouldnt need the "long size.." you could be on a 128 bit vector cpu or something that can hold ZB and larger ;) – RandomNickName42 Jul 11 '09 at 08:01
  • 4
    Bitshifting was more efficient than division in the days of C coding on the metal. Have you done a perf test in .NET to see if the bitshift really is more efficient? Not too long ago, I looked at the state of the xor-swap and found it was actually slower in .NET vs using a temp variable. – Pete Apr 05 '10 at 19:05
7

If you are trying to match the size as shown in Windows Explorer's detail view, this is the code you want:

[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
private static extern long StrFormatKBSize(
    long qdw,
    [MarshalAs(UnmanagedType.LPTStr)] StringBuilder pszBuf,
    int cchBuf);

public static string BytesToString(long byteCount)
{
    var sb = new StringBuilder(32);
    StrFormatKBSize(byteCount, sb, sb.Capacity);
    return sb.ToString();
}

This will not only match Explorer exactly but will also provide the strings translated for you and match differences in Windows versions (for example in Win10, K = 1000 vs. previous versions K = 1024).

Metalogic
  • 438
  • 4
  • 15
  • This code does not compiles, you need to specify dll from which function came from. So whole function prototype sounds like this: [DllImport("shlwapi.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern long StrFormatKBSize(long qdw, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder pszBuf, int cchBuf); Let me be the first who will favor this solution. Why to reinvent the wheel if wheel was already invented ? This is typical approach of all C# programmers, but unfortunately C# does not reach all targets which C++ reaches. – TarmoPikaro Apr 04 '16 at 03:14
  • And one more bugfix : Int64.MaxValue reaches 9,223,372,036,854,775,807, which requires to allocate buffer size of 25+ - I've rounded it to 32 just in case (not 11 like in demo code above). – TarmoPikaro Apr 04 '16 at 03:19
  • Thanks @TarmoPikaro. When I copied from my working code I missed the DllImport. Also increased the buffer size per your recommendation. Good catch! – Metalogic Apr 05 '16 at 18:30
  • impressive approach – tbhaxor May 08 '19 at 18:30
  • This shows only KB unit. The idea is to show the biggest unit depending on the value. – jstuardo Aug 15 '19 at 16:54
  • For that, you can take my code above and change `StrFormatKBSize()` with `StrFormatByteSize()`. – Metalogic Aug 16 '19 at 17:33
5

Mixture of all solutions :-)

    /// <summary>
    /// Converts a numeric value into a string that represents the number expressed as a size value in bytes,
    /// kilobytes, megabytes, or gigabytes, depending on the size.
    /// </summary>
    /// <param name="fileSize">The numeric value to be converted.</param>
    /// <returns>The converted string.</returns>
    public static string FormatByteSize(double fileSize)
    {
        FileSizeUnit unit = FileSizeUnit.B;
        while (fileSize >= 1024 && unit < FileSizeUnit.YB)
        {
            fileSize = fileSize / 1024;
            unit++;
        }
        return string.Format("{0:0.##} {1}", fileSize, unit);
    }

    /// <summary>
    /// Converts a numeric value into a string that represents the number expressed as a size value in bytes,
    /// kilobytes, megabytes, or gigabytes, depending on the size.
    /// </summary>
    /// <param name="fileInfo"></param>
    /// <returns>The converted string.</returns>
    public static string FormatByteSize(FileInfo fileInfo)
    {
        return FormatByteSize(fileInfo.Length);
    }
}

public enum FileSizeUnit : byte
{
    B,
    KB,
    MB,
    GB,
    TB,
    PB,
    EB,
    ZB,
    YB
}
NET3
  • 1,370
  • 15
  • 25
4

There is one open source project which can do that and much more.

7.Bits().ToString();         // 7 b
8.Bits().ToString();         // 1 B
(.5).Kilobytes().Humanize();   // 512 B
(1000).Kilobytes().ToString(); // 1000 KB
(1024).Kilobytes().Humanize(); // 1 MB
(.5).Gigabytes().Humanize();   // 512 MB
(1024).Gigabytes().ToString(); // 1 TB

http://humanizr.net/#bytesize

https://github.com/MehdiK/Humanizer

Jernej Novak
  • 2,926
  • 1
  • 31
  • 40
4

Like @NET3's solution. Use shift instead of division to test the range of bytes, because division takes more CPU cost.

private static readonly string[] UNITS = new string[] { "B", "KB", "MB", "GB", "TB", "PB", "EB" };

public static string FormatSize(ulong bytes)
{
    int c = 0;
    for (c = 0; c < UNITS.Length; c++)
    {
        ulong m = (ulong)1 << ((c + 1) * 10);
        if (bytes < m)
            break;
    }

    double n = bytes / (double)((ulong)1 << (c * 10));
    return string.Format("{0:0.##} {1}", n, UNITS[c]);
}
3

I use the Long extension method below to convert to a human readable size string. This method is the C# implementation of the Java solution of this same question posted on Stack Overflow, here.

/// <summary>
/// Convert a byte count into a human readable size string.
/// </summary>
/// <param name="bytes">The byte count.</param>
/// <param name="si">Whether or not to use SI units.</param>
/// <returns>A human readable size string.</returns>
public static string ToHumanReadableByteCount(
    this long bytes
    , bool si
)
{
    var unit = si
        ? 1000
        : 1024;

    if (bytes < unit)
    {
        return $"{bytes} B";
    }

    var exp = (int) (Math.Log(bytes) / Math.Log(unit));

    return $"{bytes / Math.Pow(unit, exp):F2} " +
           $"{(si ? "kMGTPE" : "KMGTPE")[exp - 1] + (si ? string.Empty : "i")}B";
}
masterwok
  • 4,131
  • 3
  • 26
  • 37
2

I assume you're looking for "1.4 MB" instead of "1468006 bytes"?

I don't think there is a built-in way to do that in .NET. You'll need to just figure out which unit is appropriate, and format it.

Edit: Here's some sample code to do just that:

http://www.codeproject.com/KB/cpp/formatsize.aspx

Peter Crabtree
  • 903
  • 6
  • 5
2

How about some recursion:

private static string ReturnSize(double size, string sizeLabel)
{
  if (size > 1024)
  {
    if (sizeLabel.Length == 0)
      return ReturnSize(size / 1024, "KB");
    else if (sizeLabel == "KB")
      return ReturnSize(size / 1024, "MB");
    else if (sizeLabel == "MB")
      return ReturnSize(size / 1024, "GB");
    else if (sizeLabel == "GB")
      return ReturnSize(size / 1024, "TB");
    else
      return ReturnSize(size / 1024, "PB");
  }
  else
  {
    if (sizeLabel.Length > 0)
      return string.Concat(size.ToString("0.00"), sizeLabel);
    else
      return string.Concat(size.ToString("0.00"), "Bytes");
  }
}

Then you call it:

return ReturnSize(size, string.Empty);
RooiWillie
  • 2,009
  • 1
  • 27
  • 34
1

My 2 cents:

  • The prefix for kilobyte is kB (lowercase K)
  • Since these functions are for presentation purposes, one should supply a culture, for example: string.Format(CultureInfo.CurrentCulture, "{0:0.##} {1}", fileSize, unit);
  • Depending on the context a kilobyte can be either 1000 or 1024 bytes. The same goes for MB, GB, etc.
j0k
  • 21,914
  • 28
  • 75
  • 84
Berend
  • 11
  • 1
  • 3
    A kilobyte means 1000 bytes (http://www.wolframalpha.com/input/?i=kilobyte), it does not depend on context. It *historically* depended on context, as wikipedia says, and it was de jure changed in 1998 and de facto change started around 2005 when terabyte hard drives brought it to public attention. The term for 1024 bytes is kibibyte. Code which switches them based on culture is producing incorrect information. – Superbest Sep 25 '12 at 18:02
1

One more approach, for what it's worth. I liked @humbads optimized solution referenced above, so have copied the principle, but I've implemented it a little differently.

I suppose it's debatable as to whether it should be an extension method (since not all longs are necessarily byte sizes), but I like them, and it's somewhere I can find the method when I next need it!

Regarding the units, I don't think I've ever said 'Kibibyte' or 'Mebibyte' in my life, and while I'm skeptical of such enforced rather than evolved standards, I suppose it'll avoid confusion in the long term.

public static class LongExtensions
{
    private static readonly long[] numberOfBytesInUnit;
    private static readonly Func<long, string>[] bytesToUnitConverters;

    static LongExtensions()
    {
        numberOfBytesInUnit = new long[6]    
        {
            1L << 10,    // Bytes in a Kibibyte
            1L << 20,    // Bytes in a Mebibyte
            1L << 30,    // Bytes in a Gibibyte
            1L << 40,    // Bytes in a Tebibyte
            1L << 50,    // Bytes in a Pebibyte
            1L << 60     // Bytes in a Exbibyte
        };

        // Shift the long (integer) down to 1024 times its number of units, convert to a double (real number), 
        // then divide to get the final number of units (units will be in the range 1 to 1023.999)
        Func<long, int, string> FormatAsProportionOfUnit = (bytes, shift) => (((double)(bytes >> shift)) / 1024).ToString("0.###");

        bytesToUnitConverters = new Func<long,string>[7]
        {
            bytes => bytes.ToString() + " B",
            bytes => FormatAsProportionOfUnit(bytes, 0) + " KiB",
            bytes => FormatAsProportionOfUnit(bytes, 10) + " MiB",
            bytes => FormatAsProportionOfUnit(bytes, 20) + " GiB",
            bytes => FormatAsProportionOfUnit(bytes, 30) + " TiB",
            bytes => FormatAsProportionOfUnit(bytes, 40) + " PiB",
            bytes => FormatAsProportionOfUnit(bytes, 50) + " EiB",
        };
    }

    public static string ToReadableByteSizeString(this long bytes)
    {
        if (bytes < 0)
            return "-" + Math.Abs(bytes).ToReadableByteSizeString();

        int counter = 0;
        while (counter < numberOfBytesInUnit.Length)
        {
            if (bytes < numberOfBytesInUnit[counter])
                return bytesToUnitConverters[counter](bytes);
            counter++;
        }
        return bytesToUnitConverters[counter](bytes);
    }
}
Giles
  • 1,077
  • 1
  • 10
  • 25
1

In order to get the human-readable string exactly as the user's used to in his Windows environment, you should use StrFormatByteSize():

using System.Runtime.InteropServices;

...

private long mFileSize;

[DllImport("Shlwapi.dll", CharSet = CharSet.Auto)]
public static extern int StrFormatByteSize(
    long fileSize,
    [MarshalAs(UnmanagedType.LPTStr)] StringBuilder buffer,
    int bufferSize);
    
public string HumanReadableFileSize
{
    get
    {
        var sb = new StringBuilder(20);
        StrFormatByteSize(mFileSize, sb, 20);
        return sb.ToString();
    }
}

I found this here: http://csharphelper.com/blog/2014/07/format-file-sizes-in-kb-mb-gb-and-so-forth-in-c/

Kim Homann
  • 2,444
  • 1
  • 12
  • 19
0

Here is a method with Log10:

using System;

class Program {
   static string NumberFormat(double n) {
      var n2 = (int)Math.Log10(n) / 3;
      var n3 = n / Math.Pow(1e3, n2);
      return String.Format("{0:f3}", n3) + new[]{"", " k", " M", " G"}[n2];
   }

   static void Main() {
      var s = NumberFormat(9012345678);
      Console.WriteLine(s == "9.012 G");
   }
}

https://docs.microsoft.com/dotnet/api/system.math.log10

Steven Penny
  • 82,115
  • 47
  • 308
  • 348