167

Just wondering if .NET provides a clean way to do this:

int64 x = 1000000;
string y = null;
if (x / 1024 == 0) {
    y = x + " bytes";
}
else if (x / (1024 * 1024) == 0) {
    y = string.Format("{0:n1} KB", x / 1024f);
}

etc...

wonea
  • 4,783
  • 17
  • 86
  • 139
John Smith
  • 4,416
  • 7
  • 41
  • 56
  • 4
    You may consider the `UnitsNet` nuget package https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/InformationUnit.g.cs – fiat Sep 02 '21 at 23:38
  • 2
    @fiat in case someone decides to give the UnitsNet package a try, there's the `Information` class that implements the `FromBytes` method, which allows you to convert from bytes to another unit, e.g. `double result = Information.FromBytes(1547821).Megabytes;` => this will return 1.547 (MB). – Jesús Hagiwara Jul 15 '22 at 21:50

30 Answers30

242

Here is a fairly concise way to do this:

static readonly string[] SizeSuffixes = 
                   { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
static string SizeSuffix(Int64 value, int decimalPlaces = 1)
{
    if (decimalPlaces < 0) { throw new ArgumentOutOfRangeException("decimalPlaces"); }
    if (value < 0) { return "-" + SizeSuffix(-value, decimalPlaces); } 
    if (value == 0) { return string.Format("{0:n" + decimalPlaces + "} bytes", 0); }

    // mag is 0 for bytes, 1 for KB, 2, for MB, etc.
    int mag = (int)Math.Log(value, 1024);

    // 1L << (mag * 10) == 2 ^ (10 * mag) 
    // [i.e. the number of bytes in the unit corresponding to mag]
    decimal adjustedSize = (decimal)value / (1L << (mag * 10));

    // make adjustment when the value is large enough that
    // it would round up to 1000 or more
    if (Math.Round(adjustedSize, decimalPlaces) >= 1000)
    {
        mag += 1;
        adjustedSize /= 1024;
    }

    return string.Format("{0:n" + decimalPlaces + "} {1}", 
        adjustedSize, 
        SizeSuffixes[mag]);
}

And here's the original implementation I suggested, which may be marginally slower, but a bit easier to follow:

static readonly string[] SizeSuffixes = 
                  { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };

static string SizeSuffix(Int64 value, int decimalPlaces = 1)
{
    if (value < 0) { return "-" + SizeSuffix(-value, decimalPlaces); } 

    int i = 0;
    decimal dValue = (decimal)value;
    while (Math.Round(dValue, decimalPlaces) >= 1000)
    {
        dValue /= 1024;
        i++;
    }

    return string.Format("{0:n" + decimalPlaces + "} {1}", dValue, SizeSuffixes[i]);
}

Console.WriteLine(SizeSuffix(100005000L));

One thing to bear in mind - in SI notation, "kilo" usually uses a lowercase k while all of the larger units use a capital letter. Windows uses KB, MB, GB, so I have used KB above, but you may consider kB instead.

JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • The asker is only looking for 1 decimal place of accuracy. Could you give an example of an input that produces an incorrect output? – JLRishe Jan 23 '13 at 20:51
  • 2
    Both examples now use floating point division so there should be much less concern about rounding errors. – JLRishe Mar 11 '14 at 06:57
  • Thank you, just what I was looking for. (2nd implementation.) – snapplex Apr 10 '14 at 14:10
  • 1
    Very neat implementation. Note that if you pass the value 0 to this function it will throw an IndexOutOfRangeException. I decided to add a `if (value == 0) { return "0"; }` check inside the function. – bounav Aug 06 '14 at 15:02
  • 1
    Can you provide the case when file size is < 0 ? For me it looks weird... – Ruslan F. May 31 '19 at 11:23
  • 1
    @RuslanF. The difference in size between two files, or any size differential can be a negative number, so there's one use case. It's just one extra line to handle the < 0 case gracefully and I think that's better than having the method blow up with an IndexOutOfRangeException (in the first example) or do nothing useful at all (in the second example). – JLRishe Jun 04 '19 at 19:19
  • Unlike the other values the k in kB is usually lowercase. :-) https://en.wikipedia.org/wiki/Kilobyte. – SharpC Jun 03 '20 at 07:56
  • 1
    @SharpC Thanks, good point. Windows uses KB, so I'm going to leave it that way, but I have added a note. – JLRishe Jun 03 '20 at 08:24
  • @RuslanF - going along with @JLRishe's reply: `Microsoft.Extensions.FileProviders.IFileInfo.Length` can return "-1 for a directory or non-existing files", too. – Frank Alvaro Jun 01 '22 at 13:55
143

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
  • 39,496
  • 45
  • 145
  • 213
  • 7
    Simple to use and understand, and it works with .Net 4.0 and up. – The Joker Nov 20 '14 at 06:11
  • 54
    This should be included as part of the .NET framework – helios456 May 02 '17 at 13:59
  • The only problem I see is that the conversion methods only work from non-byte to byte, but no the other way round. – SuperJMN Jul 10 '18 at 17:14
  • @SuperJMN what do you mean non-byte? Like bits? There’s a .FromBits method you can use. – Omar Jul 10 '18 at 17:16
  • Sorry, I have not provided much info. I'm now testing your library. It's really cool! However I don't see how to convert a value (in bytes) to a ByteSize. Which method should I use? Thanks! – SuperJMN Jul 10 '18 at 17:23
  • OK, I have just located it. It's the constructor of ByteSize! Wonderful :) – SuperJMN Jul 10 '18 at 17:24
  • 2
    If your source data is something other than "bytes" and you need to be able to convert to anything...this is is the library you should be using. – James Blake Oct 17 '18 at 16:12
  • Another problem I see with this lib as a French, is that the parsing and printing does not have translations for the units with the CultureInfos. In my language, KB is written Ko, MB -> Mo and so on. Otherwise it's really useful. – Nerpson Aug 14 '23 at 14:21
67

I would solve it using Extension methods, Math.Pow function and Enums:

public static class MyExtension
{
    public enum SizeUnits
    {
        Byte, KB, MB, GB, TB, PB, EB, ZB, YB
    }

    public static string ToSize(this Int64 value, SizeUnits unit)
    {
        return (value / (double)Math.Pow(1024, (Int64)unit)).ToString("0.00");
    }
}

and use it like:

string h = x.ToSize(MyExtension.SizeUnits.KB);
NeverHopeless
  • 11,077
  • 4
  • 35
  • 56
  • 9
    Elegant solution! – yossico Nov 14 '17 at 08:15
  • 1
    I used your idea to create one that automatically determines the unit. +1 – Louis Somers Feb 09 '18 at 14:09
  • 4
    That's a very elegant solution, which is much cleaner and consise that the approved solution. However, strictly speaking based on the enum values it should be based on power of 1000, i.e. not 1024 (https://en.wikipedia.org/wiki/Terabyte) code... public static string ToSize(this long value, Unit unit) => $"{value / Math.Pow(1000, (long) unit):F2}{unit.ToString()}"; – stoj Mar 09 '18 at 08:21
  • public enum SizeUnits { Byte, KB, MB, GB, TB, PB, EB, ZB, YB } public static string ConvertBytesTo(this long valueInBytes, SizeUnits unit, short decimalPlaces = 3) { return $"{(valueInBytes / Math.Pow(1024, (double)unit)).ToString($"n{decimalPlaces}")} {unit}"; } – Pablo de Castro Barbosa Aug 25 '23 at 04:33
47

Since everyone else is posting their methods, I figured I'd post the extension method I usually use for this:

EDIT: added int/long variants...and fixed a copypasta typo...

public static class Ext
{
    private const long OneKb = 1024;
    private const long OneMb = OneKb * 1024;
    private const long OneGb = OneMb * 1024;
    private const long OneTb = OneGb * 1024;

    public static string ToPrettySize(this int value, int decimalPlaces = 0)
    {
        return ((long)value).ToPrettySize(decimalPlaces);
    }

    public static string ToPrettySize(this long value, int decimalPlaces = 0)
    {
        var asTb = Math.Round((double)value / OneTb, decimalPlaces);
        var asGb = Math.Round((double)value / OneGb, decimalPlaces);
        var asMb = Math.Round((double)value / OneMb, decimalPlaces);
        var asKb = Math.Round((double)value / OneKb, decimalPlaces);
        string chosenValue = asTb > 1 ? string.Format("{0}Tb",asTb)
            : asGb > 1 ? string.Format("{0}Gb",asGb)
            : asMb > 1 ? string.Format("{0}Mb",asMb)
            : asKb > 1 ? string.Format("{0}Kb",asKb)
            : string.Format("{0}B", Math.Round((double)value, decimalPlaces));
        return chosenValue;
    }
}
JerKimball
  • 16,584
  • 3
  • 43
  • 55
38

I know this is old thread already. but maybe someone will look for solution. And here's what I use and the easiest way

public static string FormatFileSize(long bytes)
{
    var unit = 1024;
    if (bytes < unit) { return $"{bytes} B"; }

    var exp = (int)(Math.Log(bytes) / Math.Log(unit));
    return $"{bytes / Math.Pow(unit, exp):F2} {("KMGTPE")[exp - 1]}B";
}

Get folder size (for example usage)

public static long GetFolderSize(string path, string ext, bool AllDir)
{
    var option = AllDir ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
    return new DirectoryInfo(path).EnumerateFiles("*" + ext, option).Sum(file => file.Length);
}

EXAMPLE USAGE:

public static void TEST()
{
    string folder = @"C:\Users\User\Videos";

    var bytes = GetFolderSize(folder, "mp4", true); //or GetFolderSize(folder, "mp4", false) to get all single folder only
    var totalFileSize = FormatFileSize(bytes);
    Console.WriteLine(totalFileSize);
}
zackmark15
  • 657
  • 6
  • 12
8

The short version of the most voted answer has problems with TB values.

I adjusted it appropriately to handle also tb values and still without a loop and also added a little error checking for negative values. Here's my solution:

static readonly string[] SizeSuffixes = { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
static string SizeSuffix(long value, int decimalPlaces = 0)
{
    if (value < 0)
    {
        throw new ArgumentException("Bytes should not be negative", "value");
    }
    var mag = (int)Math.Max(0, Math.Log(value, 1024));
    var adjustedSize = Math.Round(value / Math.Pow(1024, mag), decimalPlaces);
    return String.Format("{0} {1}", adjustedSize, SizeSuffixes[mag]);
}
Roemer
  • 2,012
  • 1
  • 24
  • 26
8

Updated for C# 9.0 Relational Patterns

public const long OneKB = 1024;

public const long OneMB = OneKB * OneKB;

public const long OneGB = OneMB * OneKB;

public const long OneTB = OneGB * OneKB;

public static string BytesToHumanReadable(ulong bytes)
{
    return bytes switch
    {
        (< OneKB) => $"{bytes}B",
        (>= OneKB) and (< OneMB) => $"{bytes / OneKB}KB",
        (>= OneMB) and (< OneGB) => $"{bytes / OneMB}MB",
        (>= OneGB) and (< OneTB) => $"{bytes / OneMB}GB",
        (>= OneTB) => $"{bytes / OneTB}"
        //...
    };
}
Cryolithic
  • 151
  • 2
  • 4
7

@Servy's answer was nice and succinct. I think it can be even simpler?

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

public static string ToSize(double number, int precision = 2)
{
    // unit's number of bytes
    const double unit = 1024;
    // suffix counter
    int i = 0;
    // as long as we're bigger than a unit, keep going
    while(number > unit)
    {
        number /= unit;
        i++;
    }
    // apply precision and current suffix
    return Math.Round(number, precision) + suffixes[i];
}
drzaus
  • 24,171
  • 16
  • 142
  • 201
5

No. Mostly because it's of a rather niche need, and there are too many possible variations. (Is it "KB", "Kb" or "Ko"? Is a megabyte 1024 * 1024 bytes, or 1024 * 1000 bytes? -- yes, some places use that!)

James Curran
  • 101,701
  • 37
  • 181
  • 258
5

Here is an option that's easier to extend than yours, but no, there is none built into the library itself.

private static List<string> suffixes = new List<string> { " B", " KB", " MB", " GB", " TB", " PB" };
public static string Foo(int number)
{
    for (int i = 0; i < suffixes.Count; i++)
    {
        int temp = number / (int)Math.Pow(1024, i + 1);
        if (temp == 0)
            return (number / (int)Math.Pow(1024, i)) + suffixes[i];
    }
    return number.ToString();
}
Servy
  • 202,030
  • 26
  • 332
  • 449
4
    private string GetFileSize(double byteCount)
    {
        string size = "0 Bytes";
        if (byteCount >= 1073741824.0)
            size = String.Format("{0:##.##}", byteCount / 1073741824.0) + " GB";
        else if (byteCount >= 1048576.0)
            size = String.Format("{0:##.##}", byteCount / 1048576.0) + " MB";
        else if (byteCount >= 1024.0)
            size = String.Format("{0:##.##}", byteCount / 1024.0) + " KB";
        else if (byteCount > 0 && byteCount < 1024.0)
            size = byteCount.ToString() + " Bytes";

        return size;
    }

    private void btnBrowse_Click(object sender, EventArgs e)
    {
        if (openFile1.ShowDialog() == DialogResult.OK)
        {
            FileInfo thisFile = new FileInfo(openFile1.FileName);

            string info = "";

            info += "File: " + Path.GetFileName(openFile1.FileName);
            info += Environment.NewLine;
            info += "File Size: " + GetFileSize((int)thisFile.Length);

            label1.Text = info;
        }
    }

This is one way to do it aswell (The number 1073741824.0 is from 1024*1024*1024 aka GB)

Lobbe
  • 71
  • 1
  • 11
3

Based on NeverHopeless's elegant solution:

private static readonly KeyValuePair<long, string>[] Thresholds = 
{
    // new KeyValuePair<long, string>(0, " Bytes"), // Don't devide by Zero!
    new KeyValuePair<long, string>(1, " Byte"),
    new KeyValuePair<long, string>(2, " Bytes"),
    new KeyValuePair<long, string>(1024, " KB"),
    new KeyValuePair<long, string>(1048576, " MB"), // Note: 1024 ^ 2 = 1026 (xor operator)
    new KeyValuePair<long, string>(1073741824, " GB"),
    new KeyValuePair<long, string>(1099511627776, " TB"),
    new KeyValuePair<long, string>(1125899906842620, " PB"),
    new KeyValuePair<long, string>(1152921504606850000, " EB"),

    // These don't fit into a int64
    // new KeyValuePair<long, string>(1180591620717410000000, " ZB"), 
    // new KeyValuePair<long, string>(1208925819614630000000000, " YB") 
};

/// <summary>
/// Returns x Bytes, kB, Mb, etc... 
/// </summary>
public static string ToByteSize(this long value)
{
    if (value == 0) return "0 Bytes"; // zero is plural
    for (int t = Thresholds.Length - 1; t > 0; t--)
        if (value >= Thresholds[t].Key) return ((double)value / Thresholds[t].Key).ToString("0.00") + Thresholds[t].Value;
    return "-" + ToByteSize(-value); // negative bytes (common case optimised to the end of this routine)
}

Maybe there are excessive comments, but I tend to leave them to prevent myself from making the same mistakes over on future visits...

Louis Somers
  • 2,560
  • 3
  • 27
  • 57
2

No.

But you can implement like this;

    static double ConvertBytesToMegabytes(long bytes)
    {
    return (bytes / 1024f) / 1024f;
    }

    static double ConvertKilobytesToMegabytes(long kilobytes)
    {
    return kilobytes / 1024f;
    }

Also check out How to correctly convert filesize in bytes into mega or gigabytes?

Community
  • 1
  • 1
Soner Gönül
  • 97,193
  • 102
  • 206
  • 364
1

I have combined some of the answers here into two methods that work great. The second method below will convert from a bytes string (like 1.5.1 GB) back to bytes (like 1621350140) as a long type value. I hope this is useful to others looking for a solution to convert bytes to a string and back into bytes.

public static string BytesAsString(float bytes)
{
    string[] suffix = { "B", "KB", "MB", "GB", "TB" };
    int i;
    double doubleBytes = 0;

    for (i = 0; (int)(bytes / 1024) > 0; i++, bytes /= 1024)
    {
        doubleBytes = bytes / 1024.0;
    }

    return string.Format("{0:0.00} {1}", doubleBytes, suffix[i]);
}

public static long StringAsBytes(string bytesString)
{
    if (string.IsNullOrEmpty(bytesString))
    {
        return 0;
    }

    const long OneKb = 1024;
    const long OneMb = OneKb * 1024;
    const long OneGb = OneMb * 1024;
    const long OneTb = OneGb * 1024;
    double returnValue;
    string suffix = string.Empty;

    if (bytesString.IndexOf(" ") > 0)
    {
        returnValue = float.Parse(bytesString.Substring(0, bytesString.IndexOf(" ")));
        suffix = bytesString.Substring(bytesString.IndexOf(" ") + 1).ToUpperInvariant();
    }
    else
    {
        returnValue = float.Parse(bytesString.Substring(0, bytesString.Length - 2));
        suffix = bytesString.ToUpperInvariant().Substring(bytesString.Length - 2);
    }

    switch (suffix)
    {
        case "KB":
            {
                returnValue *= OneKb;
                break;
            }

        case "MB":
            {
                returnValue *= OneMb;
                break;
            }

        case "GB":
            {
                returnValue *= OneGb;
                break;
            }

        case "TB":
            {
                returnValue *= OneTb;
                break;
            }

        default:
            {
                break;
            }
    }

    return Convert.ToInt64(returnValue);
}
mesterak
  • 41
  • 4
1

I went for JerKimballs solution, and thumbs up to that. However, I would like to add / point out that this is indeed a matter of controversy as a whole. In my research (for other reasons) I have come up with the following pieces of information.

When normal people (I have heard they exist) speak of gigabytes they refer to the metric system wherein 1000 to the power of 3 from the original number of bytes == the number of gigabytes. However, of course there is the IEC / JEDEC standards which is nicely summed up in wikipedia, which instead of 1000 to the power of x they have 1024. Which for physical storage devices (and I guess logical such as amazon and others) means an ever increasing difference between metric vs IEC. So for instance 1 TB == 1 terabyte metric is 1000 to the power of 4, but IEC officially terms the similar number as 1 TiB, tebibyte as 1024 to the power of 4. But, alas, in non-technical applications (I would go by audience) the norm is metric, and in my own app for internal use currently I explain the difference in documentation. But for display purposes I do not even offer anything but metric. Internally even though it's not relevant in my app I only store bytes and do the calculation for display.

As a side note I find it somewhat lackluster that the .Net framework AFAIK (and I am frequently wrong thank the powers that be) even in it's 4.5 incarnation does not contain anything about this in any libraries internally. One would expect an open source library of some kind to be NuGettable at some point, but I admit this is a small peeve. On the other hand System.IO.DriveInfo and others also only have bytes (as long) which is rather clear.

  • Kudos on mentioning this! I created a library for Java to do byte size conversions and hit the IEC vs SI wall there. In the end I went all in and support both. Now that I'm working in the .NET world I might port that over to C#. But until then and for educational purposes, here's a link to my Java lib: https://github.com/StFS/YummyBytes – StFS Jul 26 '21 at 12:39
1

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 can call it:

ReturnSize(size, string.Empty);
RooiWillie
  • 2,198
  • 1
  • 30
  • 36
1

I recently needed this and required to convert the in bytes to a number in long.

Usage: Byte.Kb.ToLong(1) should give 1024.

public enum Byte
{
    Kb,
    Mb,
    Gb,
    Tb
}

public static class ByteSize
{
    private const long OneKb = 1024;
    private const long OneMb = OneKb * 1024;
    private const long OneGb = OneMb * 1024;
    private const long OneTb = OneGb * 1024;

    public static long ToLong(this Byte size, int value)
    {
        return size switch
        {
            Byte.Kb => value * OneKb,
            Byte.Mb => value * OneMb,
            Byte.Gb => value * OneGb,
            Byte.Tb => value * OneTb,
            _ => throw new NotImplementedException("This should never be hit.")
        };
    }
}

Tests using xunit:

[Theory]
[InlineData(Byte.Kb, 1, 1024)]
[InlineData(Byte.Kb, 2, 2048)]
[InlineData(Byte.Mb, 1, 1048576)]
[InlineData(Byte.Mb, 2, 2097152)]
[InlineData(Byte.Gb, 1, 1073741824)]
[InlineData(Byte.Gb, 2, 2147483648)]
[InlineData(Byte.Tb, 1, 1099511627776)]
[InlineData(Byte.Tb, 2, 2199023255552)]
public void ToLong_WhenConverting_ShouldMatchExpected(Byte size, int value, long expected)
{
    var result = size.ToLong(value);

    result.Should().Be(expected);
}
Questioning
  • 1,903
  • 1
  • 29
  • 50
0

How about:

public void printMB(uint sizekB)   
{
    double sizeMB = (double) sizekB / 1024;
    Console.WriteLine("Size is " + sizeMB.ToString("0.00") + "MB");
}

E.g. call like

printMB(123456);

Will result in output

"Size is 120,56 MB"
Boern
  • 7,233
  • 5
  • 55
  • 86
0

https://github.com/logary/logary/blob/master/src/Logary/DataModel.fs#L832-L837

let scaleBytes (value : float) : float * string =
    let log2 x = log x / log 2.
    let prefixes = [| ""; "Ki"; "Mi"; "Gi"; "Ti"; "Pi" |] // note the capital K and the 'i'
    let index = int (log2 value) / 10
    1. / 2.**(float index * 10.),
sprintf "%s%s" prefixes.[index] (Units.symbol Bytes)

(DISCLAIMER: I wrote this code, even the code in the link!)

Henrik
  • 9,714
  • 5
  • 53
  • 87
0
public static class MyExtension
{
    public static string ToPrettySize(this float Size)
    {
        return ConvertToPrettySize(Size, 0);
    }
    public static string ToPrettySize(this int Size)
    {
        return ConvertToPrettySize(Size, 0);
    }
    private static string ConvertToPrettySize(float Size, int R)
    {
        float F = Size / 1024f;
        if (F < 1)
        {
            switch (R)
            {
                case 0:
                    return string.Format("{0:0.00} byte", Size);
                case 1:
                    return string.Format("{0:0.00} kb", Size);
                case 2:
                    return string.Format("{0:0.00} mb", Size);
                case 3:
                    return string.Format("{0:0.00} gb", Size);
            }
        }
        return ConvertToPrettySize(F, ++R);
    }
}
phucng
  • 1
  • 1
0

As posted above, the recursion is the favorite way, with the help of logarithm.

The following function has 3 arguments : the input, the dimension constraint of the output, that is the third argument.

int ByteReDim(unsigned long ival, int constraint, unsigned long *oval)
{
    int base = 1 + (int) log10(ival);

    (*oval) = ival;
    if (base > constraint) {
        (*oval) = (*oval) >> 10;
        return(1 + ByteReDim((*oval), constraint, oval));
    } else
        return(0);
}

Now let's convert 12GB of RAM in several units:

int main(void)
{
    unsigned long RAM;
    int unit; // index of below symbols array
    char symbol[5] = {'B', 'K', 'M', 'G', 'T'};

    unit = ByteReDim(12884901888, 12, &RAM);
    printf("%lu%c\n", RAM, symbol[unit]); // output is 12884901888B

    unit = ByteReDim(12884901888, 9, &RAM);
    printf("%lu%c\n", RAM, symbol[unit]); // output is 12582912K

    unit = ByteReDim(12884901888, 6, &RAM);
    printf("%lu%c\n", RAM, symbol[unit]); // output is 12288M

    unit = ByteReDim(12884901888, 3, &RAM);
    printf("%lu%c\n", RAM, symbol[unit]); // output is 12G
}
CyrIng
  • 343
  • 3
  • 8
0

I use this for Windows (binary prefixes):

static readonly string[] BinaryPrefix = { "bytes", "KB", "MB", "GB", "TB" }; // , "PB", "EB", "ZB", "YB"
string GetMemoryString(double bytes)
{
    int counter = 0;
    double value = bytes;
    string text = "";
    do
    {
        text = value.ToString("0.0") + " " + BinaryPrefix[counter];
        value /= 1024;
        counter++;
    }
    while (Math.Floor(value) > 0 && counter < BinaryPrefix.Length);
    return text;
}
A.J.Bauer
  • 2,803
  • 1
  • 26
  • 35
0

I have incorporated this (with little to no modification) into a UWP DataBinding Converter for my project and thought it might also be useful to others.

The code is:

using System;
using System.Text;
using Windows.UI.Xaml.Data;

namespace MyApp.Converters
{
    public class ByteSizeConverter : IValueConverter
    {
        static readonly string[] sSizeSuffixes = { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };

        // The number of decimal places the formatter should include in the scaled output - default 1dp
        public int DecimalPlaces { get; set; } = 1;

        public object Convert(object value, Type targetType, object parameter, string language)
        {
            Int64 intVal = System.Convert.ToInt64(value);

            return SizeSuffix(intVal);
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            // TODO: Parse string into number and suffix
            //       Scale number by suffix multiplier to get bytes
            throw new NotImplementedException();
        }

        string SizeSuffix(Int64 value)
        {
            if (this.DecimalPlaces < 0) { throw new ArgumentOutOfRangeException(String.Format("DecimalPlaces = {0}", this.DecimalPlaces)); }
            if (value < 0) { return "-" + SizeSuffix(-value); }
            if (value == 0) { return string.Format("{0:n" + this.DecimalPlaces + "} bytes", 0); }

            // magnitude is 0 for bytes, 1 for KB, 2, for MB, etc.
            int magnitude = (int)Math.Log(value, 1024);
            // clip magnitude - only 8 values currently supported, this prevents out-of-bounds exception
            magnitude = Math.Min(magnitude, 8);

            // 1L << (magnitude * 10) == 2 ^ (10 * magnitude) [i.e. the number of bytes in the unit corresponding to magnitude]
            decimal adjustedSize = (decimal)value / (1L << (magnitude * 10));

            // make adjustment when the value is large enough that it would round up to 1000 or more
            if (Math.Round(adjustedSize, this.DecimalPlaces) >= 1000)
            {
                magnitude += 1;
                adjustedSize /= 1024;
            }

            return String.Format("{0:n" + this.DecimalPlaces + "} {1}", adjustedSize, sSizeSuffixes[magnitude]);
        }
    }
}

To use it, add a local resource to your UserControl or Page XAML:

<UserControl.Resources>
    <converters:ByteSizeConverter x:Key="ByteFormat" DecimalPlaces="3" />
</UserControl.Resources>

Reference it in a data binding template or data binding instance:

<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center"
    Text="{x:Bind MyItem.FileSize_bytes, Mode=OneWay, Converter={StaticResource ByteFormat}}" />

And hey presto. The magic happens.

0

Here's my spin on @drzaus's answer. I modified it to use rounding errors to our advantage and correctly manage issues around unit boundaries. It also handles negative values.

Drop this C# Program into LinqPad:

// Kudos: https://stackoverflow.com/a/48467634/117797

void Main()
{
    0.ToFriendly().Dump();                      // 0 B
    857.ToFriendly().Dump();                    // 857 B
    (173*1024).ToFriendly().Dump();             // 173 KB
    (9541*1024).ToFriendly().Dump();            // 9.32 MB
    (5261890L*1024).ToFriendly().Dump();        // 5.02 GB

    1.ToFriendly().Dump();                      // 1 B
    1024.ToFriendly().Dump();                   // 1 KB
    1048576.ToFriendly().Dump();                // 1 MB
    1073741824.ToFriendly().Dump();             // 1 GB
    1099511627776.ToFriendly().Dump();          // 1 TB
    1125899906842620.ToFriendly().Dump();       // 1 PB
    1152921504606850000.ToFriendly().Dump();    // 1 EB
}

public static class Extensions
{
    static string[] _byteUnits = new[] { "B", "KB", "MB", "GB", "TB", "PB", "EB" };

    public static string ToFriendly(this int number, int decimals = 2)
    {
        return ((double)number).ToFriendly(decimals);
    }

    public static string ToFriendly(this long number, int decimals = 2)
    {
        return ((double)number).ToFriendly(decimals);
    }

    public static string ToFriendly(this double number, int decimals = 2)
    {
        const double divisor = 1024;

        int unitIndex = 0;
        var sign = number < 0 ? "-" : string.Empty;
        var value = Math.Abs(number);
        double lastValue = number;

        while (value > 1)
        {
            lastValue = value;

            // NOTE
            // The following introduces ever increasing rounding errors, but at these scales we don't care.
            // It also means we don't have to deal with problematic rounding errors due to dividing doubles.
            value = Math.Round(value / divisor, decimals);

            unitIndex++;
        }

        if (value < 1 && number != 0)
        {
            value = lastValue;
            unitIndex--;
        }

        return $"{sign}{value} {_byteUnits[unitIndex]}";
    }
}

Output is:

0 B
857 B
173 KB
9.32 MB
1.34 MB
5.02 GB
1 B
1 KB
1 MB
1 GB
1 TB
1 PB
1 EB
Zodman
  • 3,178
  • 2
  • 32
  • 38
0
string Convert(float bytes)
{
    string[] Group = { "Bytes", "KB", "MB", "GB", "TB"};
    float B = bytes; int G = 0;
    while (B >= 1024 && G < 5)
    {
        B /= 1024;
        G += 1;
    }
    float truncated = (float)(Math.Truncate((double)B * 100.0) / 100.0);
    string load = (truncated + " " + Group[G]);

    return load;
}
Grigory Zhadko
  • 1,484
  • 1
  • 19
  • 33
joan
  • 43
  • 4
0

This is how I do it.

Console.Write(FileSizeInBytes > 1048576 ? FileSizeInBytes / 1048576f + " MB" : FileSizeInBytes / 1024f + " KB"); //1048576 = 1024 * 1024
Naresh Bisht
  • 645
  • 6
  • 16
0

I combined zackmark15's code into an all-purpose file or directory measuring approach:

public static string PathSize(string path)
{
    if (String.IsNullOrEmpty(path))
        throw new ArgumentNullException(nameof(path));

    long bytes;

    if (File.Exists(path))
        bytes = new FileInfo(path).Length;

    else if (Directory.Exists(path))
        bytes = new DirectoryInfo(path).EnumerateFiles("*", SearchOption.AllDirectories).Sum(fileInfo => fileInfo.Length);

    else
        throw new ArgumentException("Path does not exist.", nameof(path));

    const long UNIT = 1024L;

    if (bytes < UNIT)
        return $"{bytes} bytes";

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

    return $"{bytes / Math.Pow(UNIT, exp):F2} {("KMGTPE")[exp - 1]}B";
}
FocusedWolf
  • 1,062
  • 16
  • 19
0

Okay... Let's use what windows provides ...

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

public static string GetFileSize(this long number)
{
  var sb = new StringBuilder(32);
  StrFormatByteSizeW(number, sb, sb.Capacity);
  return sb.ToString();
}
Adrian Hum
  • 130
  • 2
  • 6
0

Was looking for a cleaner solution but In my opinion I can't seem to find one, so here goes.

This is intended to take a file size long value using File.Length then convert it to a size type and round to two decimal places:

10 bytes becomes 10B

1030KB becomes 1.01MB

16384KB becomes 16MB

2,097,152KB becomes 2GB

13,064,424KB becomes 12.46GB

public static string FormatFileSize(long fileSizeBytes)
{
    const int KB = 1024;

    string[] sizeLabels = { "B", "KB", "MB", "GB", "TB" };
    double fileSize = fileSizeBytes;
    int labelIndex = 0;

    while (fileSize >= KB && labelIndex < sizeLabels.Length - 1)
    {
        fileSize /= KB;
        labelIndex++;
    }

    fileSize = Math.Round(fileSize, 2);

    return $"{fileSize} {sizeLabels[labelIndex]}";
}
Paul Zahra
  • 9,522
  • 8
  • 54
  • 76
0

Since it seems to be all the rage for people to add their own algorithms as answers to this question, here's mine. (Although, admittedly, my primary motivation is to post it somewhere I can find it again when I need it!)

It's a cross-platform C# extension method that implements the Windows StrFormatByteSize function, which provides results with three or four significant figures, and up to two decimal places. It differs from StrFormatByteSize in a few minor ways (see below). Compatible with .NET 7 and later, although it wouldn't be difficult to backport it to previous versions.

If includeTotalBytes is true, a total of the number of bytes is added to the string, akin to what's shown in the Windows File Explorer's Properties dialogue.

Code

using System;
using System.Numerics;

internal static class BinaryIntegerExtensions
{
    private static readonly string[] sizeSuffixes = { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "RB", "QB" };

    /// <summary>Converts the numeric value of this instance to a string that represents the number expressed as a size-dependent value in bytes, kilobytes, megabytes, etc, up to quettabytes.</summary>
    /// <remarks>The string expression units are based on powers of 2, represented by the colloquially-understood KB, MB, GB, etc, instead of the technically correct KiB, MiB, GiB, etc.</remarks>
    /// <param name="includeTotalBytes"><c>true</c> to append the total number of bytes to the string; otherwise, <c>false</c>.</param>
    /// <returns>The string representation of the value, expressed in bytes, kilobytes, megabytes, etc, up to quettabytes.</returns>
    /// <exception cref="OverflowException">The numeric value of this instance is out of range and cannot be converted.</exception>
    internal static string ToByteSizeString<T>(this T value, bool includeTotalBytes) where T : IBinaryInteger<T>
    {
        string result;

        if (T.IsZero(value))
            result = $"0 {sizeSuffixes[0]}";
        else
        {
            int magnitude, decimalPlaces;
            double absolute, fullResult, roundedResult;
            string bytesPart = string.Empty;

            absolute = double.CreateChecked(T.Abs(value));
            magnitude = Math.Min((int)Math.Floor(Math.Log(absolute, 1024)), sizeSuffixes.Length - 1);
            fullResult = T.Sign(value) * (absolute / Math.Pow(1024, magnitude));

            decimalPlaces = Math.Max(0, 2 - (int)Math.Floor(Math.Log10(fullResult)));
            roundedResult = Math.Round(fullResult, decimalPlaces, MidpointRounding.AwayFromZero);

            if (includeTotalBytes && (magnitude > 0))
                bytesPart = $" ({value:N0} {sizeSuffixes[0]})";

            result = $"{roundedResult:#,#.##} {sizeSuffixes[magnitude]}{bytesPart}";
        }

        return (result);
    }
}

Examples of use

int test1 = 46432131;
string result1 = test1.ToByteSizeString(false);
// result1 == "44.3 MB"
ulong test2 = 1748413218964;
string result2 = test2.ToByteSizeString(false);
// result2 == "1.59 TB"
long test3 = 56431242;
string result3 = test3.ToByteSizeString(true);
// result3 == "53.8 MB (56,431,242 bytes)"
short test4 = 512;
string result4 = test4.ToByteSizeString(true);
// result4 == "512 bytes"
UInt128 test5 = UInt128.MaxValue;
string result5 = test5.ToByteSizeString(true);
// result5 = "268,435,456 QB (340,282,366,920,938,463,463,374,607,431,768,211,455 bytes)"

Differences from StrFormatByteSize

  1. Numbers in the result string have grouping where appropriate.
  2. The result is rounded up or down as appropriate, whereas StrFormatByteSize truncates after however many decimal places it displays.
  3. Negative values work as expected, whereas StrFormatByteSize provides only a total number of bytes for negative values.