333

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

shA.t
  • 16,580
  • 5
  • 54
  • 111
Larsenal
  • 49,878
  • 43
  • 152
  • 220
  • 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

27 Answers27

422

This may not the most efficient or optimized 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,638
  • 3
  • 37
  • 51
  • 16
    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
  • 23
    @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
  • 6
    @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 Nov 18 '13 at 17:53
  • 1
    @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
  • 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
  • I like that this does not use Log. Since for human-readable purposes, exact precision is not as important as speed. And besides, if precision is needed... one can always just fall back to bytes. – Joe DF Feb 07 '22 at 17:08
367

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 cases.

shA.t
  • 16,580
  • 5
  • 54
  • 111
deepee1
  • 12,878
  • 4
  • 30
  • 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
129

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
  • 3,252
  • 1
  • 27
  • 22
  • 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
  • 4
    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
75
[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
  • 97,670
  • 29
  • 122
  • 130
  • 43
    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
  • 30
    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 Sep 23 '11 at 09:14
  • 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 stands w. Palestine 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
32

Check out my 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");
TylerH
  • 20,799
  • 66
  • 75
  • 101
Omar
  • 39,496
  • 45
  • 145
  • 213
24

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
  • 27,478
  • 10
  • 60
  • 79
16

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
  • 1,258
  • 13
  • 25
14
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
  • 25,849
  • 11
  • 66
  • 104
  • 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
11

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);
    }
    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 = isISO ? 1024 : 1000;
    string unitStr = "B";
    if (bytes < unit)
    {
        return string.Format("{0} {1}", bytes, unitStr);
    }
    int exp = (int)(Math.Log(bytes) / Math.Log(unit));
    return string.Format("{0:##.##} {1}{2}{3}", bytes / Math.Pow(unit, exp), "KMGTPEZY"[exp - 1], isISO ? "i" : "", unitStr);
}
DKH
  • 452
  • 7
  • 15
  • 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
9

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
  • 3,165
  • 1
  • 33
  • 43
9
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
  • 17,420
  • 3
  • 40
  • 48
  • 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
  • 498
  • 6
  • 16
  • 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
  • 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,520
  • 1
  • 16
  • 25
5

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]);
}
alvinsay
  • 94
  • 1
  • 4
4

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,868
  • 4
  • 34
  • 41
2

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,331
  • 1
  • 15
  • 30
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
  • 899
  • 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,198
  • 1
  • 30
  • 36
2

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
  • 3,042
  • 1
  • 17
  • 20
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
  • 22,600
  • 28
  • 79
  • 90
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
  • @Superbest tell that to Windows. If you're in a Windows context, it will be 1024 for KB, so it does depend on context. – Magnetron Jun 07 '21 at 13:41
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://learn.microsoft.com/dotnet/api/system.math.log10

Zombo
  • 1
  • 62
  • 391
  • 407
0

Here is a BigInteger version of @deepee1's answer that gets around the size limitation of longs (so therefore supports yottabyte and theoretically whatever comes after that):

public static string ToBytesString(this BigInteger byteCount, string format = "N3")
{
    string[] suf = { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "YiB" };
    if (byteCount.IsZero)
    {
        return $"{0.0.ToString(format)} {suf[0]}";
    }

    var abs = BigInteger.Abs(byteCount);
    var place = Convert.ToInt32(Math.Floor(BigInteger.Log(abs, 1024)));
    var pow = Math.Pow(1024, place);

    // since we need to do this with integer math, get the quotient and remainder
    var quotient = BigInteger.DivRem(abs, new BigInteger(pow), out var remainder);
    // convert the remainder to a ratio and add both back together as doubles
    var num = byteCount.Sign * (Math.Floor((double)quotient) + ((double)remainder / pow));

    return $"{num.ToString(format)} {suf[place]}";
}
dkackman
  • 15,179
  • 13
  • 69
  • 123
0

Nothing here did exactly what I needed, and I made my own based on this thread, so here's my long extension that allows you to choose the formatting requirement depending on standard.

Definitely not the fastest but flexible. Supports up to EB/EiB.

// <summary>
/// <paramref name="byteCount"/> The original size in bytes ( 8 bits )
/// <paramref name="notationFormat"/> is supported in the following ways:
/// [    'B' / 'b' : Binary :   Kilobyte (KB) is 1024 bytes, Megabyte (MB) is 1048576 bytes, etc    ]
/// [    'I' / 'i' : IEC:       Kibibyte (KiB) is 1024 bytes, Mebibyte (MiB) is 1048576 bytes, etc    ]
/// [    'D' / 'd' : Decimal :  Kilobyte (KB) is 1000 bytes, Megabyte (MB) is 1000000 bytes, etc    ]
/// </summary>

public static string ToDataSizeString( this long byteCount, char notationFormat = 'b' )
{
    char[] supportedFormatChars = { 'b', 'i', 'd' };

    var lowerCaseNotationFormat = char.ToLowerInvariant( notationFormat );

    // Stop shooting holes in my ship!
    if ( !supportedFormatChars.Contains( lowerCaseNotationFormat ) )
    {
        throw new ArgumentException( $"notationFormat argument '{notationFormat}' not supported" );
    }

    long ebLimit = 1152921504606846976;
    long pbLimit = 1125899906842624;
    long tbLimit = 1099511627776;
    long gbLimit = 1073741824;
    long mbLimit = 1048576;
    long kbLimit = 1024;

    var ebSuffix = "EB";
    var pbSuffix = "PB";
    var tbSuffix = "TB";
    var gbSuffix = "GB";
    var mbSuffix = "MB";
    var kbSuffix = "KB";
    var bSuffix  = " B";

    switch ( lowerCaseNotationFormat )
    {
        case 'b':
            // Sweet as
            break;

        case 'i':
            // Limits stay the same, suffixes need changed
            ebSuffix = "EiB";
            pbSuffix = "PiB";
            tbSuffix = "TiB";
            gbSuffix = "GiB";
            mbSuffix = "MiB";
            kbSuffix = "KiB";
            bSuffix  = "  B";
            break;

        case 'd':
            // Suffixes stay the same, limits need changed
            ebLimit = 1000000000000000000;
            pbLimit = 1000000000000000;
            tbLimit = 1000000000000;
            gbLimit = 1000000000;
            mbLimit = 1000000;
            kbLimit = 1000;
            break;

        default:
            // Should have already Excepted, but hey whatever
            throw new ArgumentException( $"notationFormat argument '{notationFormat}' not supported" );

    }

    string fileSizeText;

    // Exa/Exbi sized
    if ( byteCount >= ebLimit )
    {
        fileSizeText = $"{( (double)byteCount / ebLimit ):N1} {ebSuffix}";
    }
    // Peta/Pebi sized
    else if ( byteCount >= pbLimit )
    {
        fileSizeText = $"{( (double)byteCount / pbLimit ):N1} {pbSuffix}";
    }
    // Tera/Tebi sized
    else if ( byteCount >= tbLimit )
    {
        fileSizeText = $"{( (double)byteCount / tbLimit ):N1} {tbSuffix}";
    }
    // Giga/Gibi sized
    else if ( byteCount >= gbLimit )
    {
        fileSizeText = $"{( (double)byteCount / gbLimit ):N1} {gbSuffix}";
    }
    // Mega/Mibi sized
    else if ( byteCount >= mbLimit )
    {
        fileSizeText = $"{( (double)byteCount / mbLimit ):N1} {mbSuffix}";
    }
    // Kilo/Kibi sized
    else if ( byteCount >= kbLimit )
    {
        fileSizeText = $"{( (double)byteCount / kbLimit ):N1} {kbSuffix}";
    }
    // Byte sized
    else
    {
        fileSizeText = $"{byteCount} {bSuffix}";
    }

    return fileSizeText;
}
kellybs1
  • 11
  • 2
0

I made up this and it works just fine.

public string[] DetermineDigitalSize(string filename)
        {
            string[] result = new string[2];
            string[] sizes = { "B", "KB", "MB", "GB", "GB" };
            double len = new FileInfo(filename).Length;
             double adjustedSize = len;
            double testSize = 0;
            int order = 0;
            while (order< sizes.Length-1)
            {
                testSize = adjustedSize / 1024;
                if (testSize >= 1) { adjustedSize = testSize; order++; }
                else { break; }
            }
            result[0] = $"{adjustedSize:f2}";
            result[1] = sizes[order];
            return result;
        }
  • 1
    This question is asked more than 4 years ago and it has accepted answer. Please add some details about the reason you are adding a new answer – MD Zand Nov 29 '22 at 20:07
  • 1
    Just trying to be helpful. A ready function for you, –  Dec 01 '22 at 00:06
0

After a lot of stuffing around with the order of magnitudes, and the number of decimal points, I have managed to get a function that closely emulates the function that windows has in the WIN32.dll library...

private string FormatBytesWithPrefix(double bytes)
    {
        string[] sizes = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
        int order = 0;
        double size = bytes;
    
        while (size >= 1024 && order < sizes.Length - 1)
        {
            order++;
            size /= 1024;
        }
    
        return $"{FormatNumberToThreeDigits(size)} {sizes[order]}";
    }
    
    private string FormatNumberToThreeDigits(double value)
    {
        if (value < 10)
        {
            // Format numbers less than 10 with 2 decimal places, rounded
            return Math.Round(value, 2).ToString("F2");
        }
        else if (value < 100)
        {
            // Format numbers between 10 and 99 with 1 decimal place, rounded
            return Math.Round(value, 1).ToString("F1");
        }
        else
        {
            // Format numbers 100 and above as whole value
            return Math.Round(value).ToString("F0");
        }
    }
Adrian Hum
  • 130
  • 2
  • 6
-1

1-liner (plus the prefixes constant)

const String prefixes = " KMGTPEY";
/// <summary> Returns the human-readable file size for an arbitrary, 64-bit file size. </summary>
public static String HumanSize(UInt64 bytes)
    => Enumerable
    .Range(0, prefixes.Length)
    .Where(i => bytes < 1024U<<(i*10))
    .Select(i => $"{(bytes>>(10*i-10))/1024:0.###} {prefixes[i]}B")
    .First();

Or, if you want to reduce LINQ object allocations, use for-loop variation of the same:

/// <summary>
/// Returns the human-readable file size for an arbitrary, 64-bit file size.
/// </summary>
public static String HumanSize(UInt64 bytes)
{
    const String prefixes = " KMGTPEY";
    for (var i = 0; i < prefixes.Length; i++)
        if (bytes < 1024U<<(i*10))
            return $"{(bytes>>(10*i-10))/1024:0.###} {prefixes[i]}B";

    throw new ArgumentOutOfRangeException(nameof(bytes));
}
David V McKay
  • 122
  • 2
  • 12
-2

This question is old, but a very fast C# function could be:

public static string PrettyPrintBytes(long numBytes)
{
    if (numBytes < 1024)
        return $"{numBytes} B";
            
    if (numBytes < 1048576)
        return $"{numBytes / 1024d:0.##} KB";

    if (numBytes < 1073741824)
        return $"{numBytes / 1048576d:0.##} MB";

    if (numBytes < 1099511627776)
        return $"{numBytes / 1073741824d:0.##} GB";

    if (numBytes < 1125899906842624)
        return $"{numBytes / 1099511627776d:0.##} TB";
            
    if (numBytes < 1152921504606846976)
        return $"{numBytes / 1125899906842624d:0.##} PB";

    return $"{numBytes / 1152921504606846976d:0.##} EB";
}

This has only one cast and one divide per call and only up to 6 compares. When benchmarking, I found that string interpolation is much faster than using String.Format().

SN74H74N
  • 1
  • 1
  • 1
    it may be fast but I wouldn't say that it's modern at all. – baltermia Mar 29 '22 at 12:59
  • @baltermia You're right. I guess I was referring to the string interpolation, which wasn't a thing when the original question was asked. – SN74H74N Mar 30 '22 at 15:16
  • If this is the fastest solution then it should include the performance tests and compare the speed with the others. And the it should be upvoted from everybody. But to be honest, this solution should include the negative/zero cases and the si format (using 1000 or 1024) – albertovilches Sep 10 '22 at 07:06