.NET fournit-il un moyen simple de convertir les octets en Ko, Mo, Go, etc.?

112

Je me demande simplement si .NET fournit un moyen propre de le faire:

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

John Smith
la source

Réponses:

193

Voici une façon assez concise de procéder:

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

Et voici l'implémentation originale que j'ai suggérée, qui peut être légèrement plus lente, mais un peu plus facile à suivre:

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

Une chose à garder à l'esprit - en notation SI, "kilo" utilise généralement un k minuscule tandis que toutes les unités plus grandes utilisent une lettre majuscule. Windows utilise KB, MB, GB, donc j'ai utilisé KB ci-dessus, mais vous pouvez envisager Ko à la place.

JLRishe
la source
Le demandeur ne recherche qu'une seule décimale pour l'exactitude. Pouvez-vous donner un exemple d'entrée qui produit une sortie incorrecte?
JLRishe
2
Les deux exemples utilisent maintenant la division en virgule flottante, donc les erreurs d'arrondi devraient être beaucoup moins préoccupantes.
JLRishe
Merci, exactement ce que je cherchais. (2ème implémentation.)
snapplex
1
Mise en œuvre très soignée. Notez que si vous passez la valeur 0 à cette fonction, elle lèvera une IndexOutOfRangeException. J'ai décidé d'ajouter une if (value == 0) { return "0"; }vérification à l'intérieur de la fonction.
bounav
Pouvez-vous indiquer le cas lorsque la taille du fichier est <0? Pour moi, ça a l'air bizarre ...
Ruslan F.
85

La caisse ByteSize bibliothèque. C'est leSystem.TimeSpan for bytes!

Il gère la conversion et le formatage pour vous.

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

Il effectue également la représentation et l'analyse de chaînes.

// 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
la source
6
Simple à utiliser et à comprendre, il fonctionne avec .Net 4.0 et plus.
Le Joker
34
Cela devrait être inclus dans le cadre du framework .NET
helios456
Le seul problème que je vois est que les méthodes de conversion ne fonctionnent que de non-octet à octet, mais pas l'inverse.
SuperJMN
@SuperJMN que voulez-vous dire sans octet? Comme des morceaux? Il existe une méthode .FromBits que vous pouvez utiliser.
Omar
1
Si vos données source sont autres que des "octets" et que vous devez être capable de convertir en n'importe quoi ... c'est la bibliothèque que vous devriez utiliser.
James Blake
38

Puisque tout le monde publie leurs méthodes, je me suis dit que je publierais la méthode d'extension que j'utilise habituellement pour cela:

EDIT: ajout de variantes int / long ... et correction d'une typo 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;
    }
}
JerKimball
la source
Gardez à l'esprit cependant que b minuscule peut généralement signifier des bits plutôt que des octets. :-) fr.wikipedia.org/wiki/Data-rate_units#Kilobit_per_second
SharpC
32

Je le résoudrais en utilisant Extension methods, Math.Powfonction et 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");
    }
}

et utilisez-le comme:

string h = x.ToSize(MyExtension.SizeUnits.KB);
NeverHopeless
la source
3
Solution élégante!
yossico
1
J'ai utilisé votre idée pour en créer une qui détermine automatiquement l'unité. +1
Louis Somers
2
C'est une solution très élégante, qui est beaucoup plus propre et constitue la solution approuvée. Cependant, strictement parlant, basé sur les valeurs d'énumération, il devrait être basé sur une puissance de 1000, c'est-à-dire pas du code 1024 ( en.wikipedia.org/wiki/Terabyte ) ... chaîne statique publique ToSize (cette valeur longue, unité unitaire) => $ "{valeur / Math.Pow (1000, (longue) unité): F2} {unit.ToString ()}";
stoj
6

La version courte de la réponse la plus votée a des problèmes avec les valeurs TB.

Je l'ai ajusté de manière appropriée pour gérer également les valeurs tb et toujours sans boucle et j'ai également ajouté une petite vérification d'erreur pour les valeurs négatives. Voici ma 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
la source
1
Le problème déclaré avec des valeurs élevées ne devrait plus être présent dans la réponse acceptée.
JLRishe
5

Non. Surtout parce qu'il s'agit d'un besoin plutôt de niche et qu'il y a trop de variations possibles. (S'agit-il de "Ko", "Kb" ou "Ko"? Un mégaoctet est-il de 1024 * 1024 octets ou 1024 * 1000 octets? - oui, certains endroits l'utilisent!)

James Curran
la source
1
+1 - selon Wikipedia , kb => 1000 octets et KiB => 1024 octets.
Peter Majeed
5

Voici une option plus facile à étendre que la vôtre, mais non, aucune n'est intégrée à la bibliothèque elle-même.

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

C'est une façon de le faire également (le numéro 1073741824.0 est de 1024 * 1024 * 1024 aka GB)

Lobbe
la source
3

La réponse de @ Servy était belle et succincte. Je pense que cela peut être encore plus simple?

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
la source
3

Basé sur la solution élégante 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)
}

Peut-être qu'il y a des commentaires excessifs, mais j'ai tendance à les laisser pour m'empêcher de faire les mêmes erreurs lors de futures visites ...

Louis Somers
la source
1

J'ai combiné certaines des réponses ici en deux méthodes qui fonctionnent très bien. La deuxième méthode ci-dessous convertira une chaîne d'octets (comme 1,5,1 Go) en octets (comme 1621350140) en tant que valeur de type long. J'espère que cela sera utile à ceux qui recherchent une solution pour convertir des octets en une chaîne et les remettre en octets.

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
la source
Puis - je demander pourquoi vous utilisez float.Parsepour double?
John_J
1

Je sais que c'est déjà un vieux fil. mais peut-être que quelqu'un cherchera une solution. Et voici ce que j'utilise et le moyen le plus simple

  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";
    }
zackmark15
la source
0

Que diriez-vous:

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

Par exemple, appelez comme

printMB(123456);

Se traduira par une sortie

"Size is 120,56 MB"
Boern
la source
0

Je suis allé chercher la solution JerKimballs, et je suis d'accord avec ça. Cependant, je voudrais ajouter / souligner que c'est effectivement une question de controverse dans son ensemble. Dans mes recherches (pour d'autres raisons), j'ai trouvé les informations suivantes.

Quand les gens normaux (j'ai entendu dire qu'ils existent) parlent de gigaoctets, ils se réfèrent au système métrique dans lequel 1000 à la puissance de 3 à partir du nombre original d'octets == le nombre de gigaoctets. Cependant, bien sûr, il y a les normes IEC / JEDEC qui se résument bien dans wikipedia, qui au lieu de 1000 à la puissance de x, elles en ont 1024. Ce qui pour les périphériques de stockage physiques (et je suppose logique comme amazon et autres) signifie un différence toujours croissante entre métrique et CEI. Ainsi, par exemple, 1 To == 1 téraoctet métrique équivaut à 1000 à la puissance de 4, mais la CEI qualifie officiellement le même nombre de 1 Tio, le tebibyte à 1024 à la puissance de 4. Mais, hélas, dans les applications non techniques (je voudrais aller par public) la norme est métrique, et dans ma propre application à usage interne, j'explique actuellement la différence dans la documentation. Mais à des fins d'affichage, je n'offre même rien d'autre que la métrique. En interne, même si ce n'est pas pertinent dans mon application, je ne stocke que les octets et je fais le calcul pour l'affichage.

En passant, je trouve quelque peu terne que le framework .Net AFAIK (et je me trompe souvent, remercie les pouvoirs en place) même dans son incarnation 4.5 ne contient rien à ce sujet dans aucune bibliothèque en interne. On pourrait s'attendre à ce qu'une bibliothèque open source quelconque soit NuGettable à un moment donné, mais j'admets que c'est une petite bête noire. D'autre part, System.IO.DriveInfo et d'autres n'ont également que des octets (aussi longs), ce qui est plutôt clair.

randomSheeple
la source
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
la source
0

Que diriez-vous d'une récursivité:

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

Ensuite, vous pouvez l'appeler:

ReturnSize(size, string.Empty);
RooiWillie
la source
0

Comme indiqué ci-dessus, la récursivité est la méthode préférée, à l'aide du logarithme.

La fonction suivante a 3 arguments: l'entrée, la contrainte de dimension de la sortie, c'est-à-dire le troisième 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);
}

Convertissons maintenant 12 Go de RAM en plusieurs unités:

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
la source
0

J'utilise ceci pour Windows (préfixes binaires):

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;
}
AJBauer
la source
0

J'ai incorporé ceci (avec peu ou pas de modification) dans un convertisseur UWP DataBinding pour mon projet et j'ai pensé que cela pourrait également être utile à d'autres.

Le code est:

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

Pour l'utiliser, ajoutez une ressource locale à votre UserControl ou Page XAML:

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

Référencez-le dans un modèle de liaison de données ou une instance de liaison de données:

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

Et hé hop. La magie opère.

FunkyLobster27
la source
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: j'ai écrit ce code, même le code dans le lien!)

Henrik
la source