¿Ofrece .NET una forma sencilla de convertir bytes a KB, MB, GB, etc.?


112

Me pregunto si .NET proporciona una forma limpia de hacer esto:

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

Respuestas:


192

Aquí hay una forma bastante concisa de hacer esto:

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

Y aquí está la implementación original que sugerí, que puede ser un poco más lenta, pero un poco más 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));

Una cosa a tener en cuenta: en la notación SI, "kilo" generalmente usa una k minúscula, mientras que todas las unidades más grandes usan una letra mayúscula. Windows usa KB, MB, GB, por lo que he usado KB arriba, pero puede considerar kB en su lugar.


El autor de la pregunta solo busca 1 decimal de precisión. ¿Podría dar un ejemplo de una entrada que produzca una salida incorrecta?
JLRishe

2
Ambos ejemplos ahora usan división de punto flotante, por lo que debería haber mucha menos preocupación por los errores de redondeo.
JLRishe

Gracias, justo lo que estaba buscando. (2da implementación)
snapplex

1
Implementación muy cuidada. Tenga en cuenta que si pasa el valor 0 a esta función, arrojará una IndexOutOfRangeException. Decidí agregar un if (value == 0) { return "0"; }cheque dentro de la función.
bounav

¿Puede proporcionar el caso cuando el tamaño del archivo es <0? Para mí se ve raro ...
Ruslan F.

84

Partida de los ByteSize biblioteca. Es elSystem.TimeSpan de bytes!

Maneja la conversión y el formato por usted.

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

También realiza representación y análisis de cadenas.

// 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
Fácil de usar y comprender, funciona con .Net 4.0 y versiones posteriores.
The Joker

34
Esto debe incluirse como parte del marco .NET
helios456

El único problema que veo es que los métodos de conversión solo funcionan de no byte a byte, pero no al revés.
SuperJMN

@SuperJMN ¿qué quieres decir sin byte? ¿Como pedacitos? Hay un método .FromBits que puede utilizar.
Omar

1
Si sus datos de origen son algo diferente a "bytes" y necesita poder convertir a cualquier cosa ... esta es la biblioteca que debe usar.
James Blake

37

Dado que todos los demás están publicando sus métodos, pensé que publicaría el método de extensión que suelo usar para esto:

EDITAR: se agregaron variantes int / long ... y se corrigió un error tipográfico de 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;
    }
}

Sin embargo, tenga en cuenta que la b minúscula normalmente puede significar bits en lugar de bytes. :-) en.wikipedia.org/wiki/Data-rate_units#Kilobit_per_second
SharpC

32

Lo resolvería usando Extension methods, Math.Powfunción y 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");
    }
}

y utilícelo como:

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

3
¡Solución elegante!
Yossico

1
Usé tu idea para crear una que determina automáticamente la unidad. +1
Louis Somers

2
Es una solución muy elegante, mucho más limpia y consistente que la solución aprobada. Sin embargo, estrictamente hablando, basado en los valores de enumeración, debería basarse en la potencia de 1000, es decir, no en el código 1024 ( en.wikipedia.org/wiki/Terabyte ) ... cadena estática pública ToSize (este valor largo, unidad de unidad) => $ "{valor / Math.Pow (1000, unidad (larga)): F2} {unit.ToString ()}";
Stoj

6

La versión corta de la respuesta más votada tiene problemas con los valores de TB.

Lo ajusté adecuadamente para manejar también los valores tb y aún sin un bucle, y también agregué una pequeña verificación de errores para valores negativos. Esta es mi solución:

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
El problema declarado con valores grandes ya no debería estar presente en la respuesta aceptada.
JLRishe

5

No. Principalmente porque se trata de una necesidad bastante específica y hay demasiadas variaciones posibles. (¿Es "KB", "Kb" o "Ko"? ¿Es un megabyte 1024 * 1024 bytes o 1024 * 1000 bytes? - ¡Sí, algunos lugares lo usan!)


1
+1 - según Wikipedia , kb => 1000 bytes y KiB => 1024 bytes.
Peter Majeed

5

Aquí hay una opción que es más fácil de ampliar que la suya, pero no, no hay ninguna integrada en la 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 es una forma de hacerlo también (el número 1073741824.0 es de 1024 * 1024 * 1024 también conocido como GB)


3

La respuesta de @ Servy fue agradable y concisa. ¿Creo que puede ser aún más sencillo?

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

Basado en la elegante solución de 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)
}

Tal vez haya comentarios excesivos, pero suelo dejarlos para no cometer los mismos errores en futuras visitas ...



1

He combinado algunas de las respuestas aquí en dos métodos que funcionan muy bien. El segundo método a continuación se convertirá de una cadena de bytes (como 1,5.1 GB) a bytes (como 1621350140) como un valor de tipo largo. Espero que esto sea útil para quienes busquen una solución para convertir bytes en una cadena y volver a convertirlos en 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);
}

¿Puedo preguntar por qué solía float.Parsehacerlo double?
John_J

1

Sé que este ya es un hilo antiguo. pero tal vez alguien busque una solución. Y esto es lo que uso y la forma más 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

Qué tal si:

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

Por ejemplo, llamar como

printMB(123456);

Resultará en salida

"Size is 120,56 MB"

0

Fui por la solución JerKimballs, y aprobé eso. Sin embargo, me gustaría añadir / señalar que, de hecho, se trata de una cuestión controvertida en su conjunto. En mi investigación (por otras razones) he encontrado la siguiente información.

Cuando la gente normal (he oído que existen) habla de gigabytes, se refiere al sistema métrico en el que 1000 elevado a 3 del número original de bytes == el número de gigabytes. Sin embargo, por supuesto, existen los estándares IEC / JEDEC que están muy bien resumidos en wikipedia, que en lugar de 1000 a la potencia de x tienen 1024. Lo que para dispositivos de almacenamiento físico (y supongo que lógicos como Amazon y otros) significa un Diferencia cada vez mayor entre el sistema métrico y el IEC. Entonces, por ejemplo, 1 TB == 1 terabyte métrica es 1000 elevado a 4, pero IEC califica oficialmente el número similar como 1 TiB, tebibyte a 1024 elevado a 4. Pero, por desgracia, en aplicaciones no técnicas (yo ir por audiencia) la norma es métrica, y en mi propia aplicación para uso interno actualmente explico la diferencia en la documentación. Pero para fines de visualización, ni siquiera ofrezco nada más que métricas. Internamente, aunque no es relevante en mi aplicación, solo almaceno bytes y hago el cálculo para la visualización.

Como nota al margen, me parece algo mediocre que el marco .Net AFAIK (y con frecuencia me equivoco gracias a los poderes), incluso en su encarnación 4.5, no contiene nada sobre esto en ninguna biblioteca internamente. Uno esperaría que una biblioteca de código abierto de algún tipo fuera NuGettable en algún momento, pero admito que esto es un pequeño inconveniente. Por otro lado, System.IO.DriveInfo y otros también solo tienen bytes (siempre que sean largos), lo cual es 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

¿Qué tal una recursividad?

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

Entonces puedes llamarlo:

ReturnSize(size, string.Empty);

0

Como se publicó anteriormente, la recursividad es la forma favorita, con la ayuda del logaritmo.

La siguiente función tiene 3 argumentos: la entrada, la restricción de dimensión de la salida, que es el tercer 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);
}

Ahora convierta 12GB de RAM en varias 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

Yo uso esto para Windows (prefijos binarios):

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

He incorporado esto (con poca o ninguna modificación) en un UWP DataBinding Converter para mi proyecto y pensé que también podría ser útil para otros.

El codigo es:

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 usarlo, agregue un recurso local a su UserControl o Page XAML:

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

Haga referencia a él en una plantilla de enlace de datos o instancia de enlace de datos:

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

Y listo. La magia sucede.


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)

(DESCARGO DE RESPONSABILIDAD: escribí este código, ¡incluso el código en el enlace!)

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.