O .NET fornece uma maneira fácil de converter bytes em KB, MB, GB, etc.?


112

Gostaria de saber se o .NET oferece uma maneira limpa de fazer isso:

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

Respostas:


192

Esta é uma maneira bastante concisa de fazer isso:

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); } 
    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]);
}

E aqui está a implementação original que sugeri, que pode ser um pouco mais lenta, mas um pouco mais fácil de seguir:

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); } 

    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));

Uma coisa a ter em mente - na notação SI, "kilo" geralmente usa uma letra k minúscula, enquanto todas as unidades maiores usam uma letra maiúscula. O Windows usa KB, MB, GB, então usei KB acima, mas você pode considerar kB.


O autor da pergunta está procurando apenas 1 casa decimal de precisão. Você poderia dar um exemplo de uma entrada que produz uma saída incorreta?
JLRishe

2
Ambos os exemplos agora usam divisão de ponto flutuante, portanto, deve haver muito menos preocupação com erros de arredondamento.
JLRishe

Obrigado, exatamente o que eu estava procurando. (2ª implementação.)
snapplex de

1
Implementação muito legal. Observe que se você passar o valor 0 para esta função, ela lançará uma IndexOutOfRangeException. Decidi adicionar um if (value == 0) { return "0"; }cheque dentro da função.
bounav

Você pode fornecer o caso em que o tamanho do arquivo é <0? Para mim, parece estranho ...
Ruslan F.

84

Confira o ByteSize biblioteca. É o System.TimeSpanpara bytes!

Ele lida com a conversão e formatação para você.

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

Ele também faz representação e análise de strings.

// 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");

6
Simples de usar e entender, e funciona com .Net 4.0 e superior.
The Joker de

33
Isso deve ser incluído como parte do .NET framework
helios456

O único problema que vejo é que os métodos de conversão funcionam apenas de não byte para byte, mas não o contrário.
SuperJMN

@SuperJMN o que você quer dizer sem byte? Como bits? Existe um método .FromBits que você pode usar.
Omar

1
Se seus dados de origem são algo diferente de "bytes" e você precisa ser capaz de converter para qualquer coisa ... esta é a biblioteca que você deve usar.
James Blake,

37

Como todo mundo está postando seus métodos, decidi postar o método de extensão que geralmente uso para isso:

EDIT: adicionadas variantes int / long ... e corrigido um erro de digitação copypasta ...

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;
    }
}

Lembre-se de que b minúsculo pode significar normalmente bits em vez de bytes. :-) en.wikipedia.org/wiki/Data-rate_units#Kilobit_per_second
SharpC

32

Eu iria resolver isso usando Extension methods, Math.Powfunction e 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");
    }
}

e usá-lo como:

string h = x.ToSize(MyExtension.SizeUnits.KB);

3
Solução elegante!
yossico

1
Usei sua ideia para criar um que determina automaticamente a unidade. +1
Louis Somers

2
É uma solução muito elegante, muito mais limpa e consistente que a solução aprovada. No entanto, estritamente falando com base nos valores enum, ele deve ser baseado na potência de 1000, ou seja, não no código 1024 ( en.wikipedia.org/wiki/Terabyte ) ... public static string ToSize (este valor longo, unidade Unit) => $ "{value / Math.Pow (1000, unidade (longa)): F2} {unit.ToString ()}";
stoj

6

A versão resumida da resposta mais votada tem problemas com os valores de TB.

Eu ajustei apropriadamente para lidar também com os valores tb e ainda sem um loop e também adicionei uma pequena verificação de erros para valores negativos. Esta é minha solução:

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]);
}

1
O problema declarado com valores grandes não deve mais estar presente na resposta aceita.
JLRishe

5

Não. Principalmente porque é uma necessidade bastante específica e existem muitas variações possíveis. (É "KB", "Kb" ou "Ko"? Tem um megabyte de 1024 * 1024 bytes ou 1024 * 1000 bytes? - sim, alguns lugares usam isso!)


1
+1 - de acordo com a Wikipedia , kb => 1000 bytes e KiB => 1024 bytes.
Peter Majeed

5

Aqui está uma opção que é mais fácil de estender do que a sua, mas não, não há nenhuma incluída na própria biblioteca.

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();
}

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;
        }
    }

Esta é uma maneira de fazer isso também (o número 1073741824.0 é de 1024 * 1024 * 1024 também conhecido como GB)


3

A resposta de @Servy foi boa e sucinta. Acho que pode ser ainda mais simples?

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];
}

3

Baseado na solução elegante do NeverHopeless:

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)
}

Talvez haja comentários excessivos, mas tendo a deixá-los para evitar cometer os mesmos erros em futuras visitas ...



1

Combinei algumas das respostas aqui em dois métodos que funcionam muito bem. O segundo método abaixo irá converter de uma string de bytes (como 1.5.1 GB) de volta para bytes (como 1621350140) como um valor de tipo longo. Espero que isso seja útil para outras pessoas que procuram uma solução para converter bytes em uma string e de volta em 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);
}

Posso perguntar por que você usa float.Parsepara double?
John_J

1

Eu sei que este é um tópico antigo. mas talvez alguém procure uma solução. E aqui está o que eu uso e a maneira mais fácil

  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";
    }

0

E se:

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

Por exemplo, ligue como

printMB(123456);

Irá resultar em saída

"Size is 120,56 MB"

0

Eu escolhi a solução JerKimballs e gostei disso. No entanto, gostaria de acrescentar / salientar que se trata de fato de uma questão controversa como um todo. Em minha pesquisa (por outras razões), encontrei as seguintes informações.

Quando pessoas normais (ouvi dizer que existem) falam de gigabytes, elas se referem ao sistema métrico em que 1000 à potência de 3 do número original de bytes == o número de gigabytes. No entanto, é claro que há os padrões IEC / JEDEC que são bem resumidos na wikipedia, que em vez de 1000 elevado a x eles têm 1024. O que para dispositivos de armazenamento físico (e eu acho lógico como Amazon e outros) significa um diferença cada vez maior entre métrica e IEC. Assim, por exemplo, 1 TB == 1 terabyte métrico é 1000 elevado à potência de 4, mas a IEC oficialmente denomina o número semelhante como 1 TiB, tebibyte como 1024 elevado à potência de 4. Mas, infelizmente, em aplicações não técnicas (eu faria ir pelo público) a norma é métrica, e no meu próprio aplicativo para uso interno atualmente eu explico a diferença na documentação. Mas, para fins de exibição, não ofereço nada além de métricas. Internamente, embora não seja relevante em meu aplicativo, eu apenas armazeno bytes e faço o cálculo para exibição.

Como uma observação lateral, acho um tanto sem brilho que o .Net framework AFAIK (e estou frequentemente errado, graças aos poderes que são), mesmo em sua encarnação 4.5, não contém nada sobre isso em nenhuma biblioteca interna. Seria de se esperar que uma biblioteca de código aberto de algum tipo fosse NuGettable em algum momento, mas admito que isso é uma pequena implicância. Por outro lado, System.IO.DriveInfo e outros também possuem apenas bytes (desde que), o que é bastante claro.



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);
    }
}

0

Que tal alguma recursão:

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");
  }
}

Então você pode chamá-lo de:

ReturnSize(size, string.Empty);

0

Conforme postado acima, a recursão é a forma preferida, com a ajuda do logaritmo.

A função a seguir tem 3 argumentos: a entrada, a restrição de dimensão da saída, que é o terceiro argumento.

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);
}

Agora vamos converter 12 GB de RAM em várias unidades:

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
}

0

Eu uso isso para Windows (prefixos binários):

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;
}

0

Eu incorporei isso (com pouca ou nenhuma modificação) em um conversor UWP DataBinding para meu projeto e pensei que também poderia ser útil para outros.

O código é:

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]);
        }
    }
}

Para usá-lo, adicione um recurso local ao seu UserControl ou Page XAML:

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

Faça referência a ele em um modelo de vinculação de dados ou instância de vinculação de dados:

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

E ei pronto. A mágica acontece.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.