101 votes

Conversion de double en chaîne de caractères sans notation scientifique

Comment convertir un double en une représentation de chaîne en virgule flottante sans notation scientifique dans le .NET Framework?

Exemples "petits" (les nombres effectifs peuvent être de n'importe quelle taille, comme 1.5E200 ou 1e-200) :

3248971234698200000000000000000000000000000000
0.00000000000000000000000000000000000023897356978234562

Aucun des formats de nombres standard ne sont comme ça, et un format personnalisé ne semble pas permettre d'avoir un nombre ouvert de chiffres après le séparateur décimal.

Ce n'est pas un doublon de Comment convertir un double en une chaîne sans la représentation de puissance de 10 (E-05) parce que les réponses données là-bas ne résolvent pas le problème en question. La solution acceptée dans cette question était d'utiliser un point fixe (comme 20 chiffres), ce qui n'est pas ce que je veux. Un format à point fixe et la suppression des zéros redondants ne résolvent pas le problème non plus car la largeur maximale pour un point fixe est de 99 caractères.

Remarque : la solution doit traiter correctement les formats de nombres personnalisés (par exemple, un autre séparateur décimal, en fonction des informations culturelles).

Edition : La question concerne vraiment uniquement l'affichage des nombres susmentionnés. Je suis conscient de comment les nombres flottants fonctionnent et quels nombres peuvent être utilisés et calculés avec eux.

1 votes

Avez-vous une solution pour cette question maintenant?

0 votes

@Anand, il existe deux solutions qui fonctionnent (Paul Sasik et la mienne) même si elles ne sont pas particulièrement "belles" (en passant par la manipulation de chaînes de caractères).

34voto

Robert Lamm Points 59

J'ai eu un problème similaire et cela a fonctionné pour moi:

doubleValue.ToString("F99").TrimEnd('0')

F99 peut être trop, mais vous avez compris l'idée.

1 votes

99 n'est pas suffisant, et cela doit fonctionner à la fois avant et après la virgule.

2 votes

TrimEnd('0') est suffisant, car le tableau de char est params. Autrement dit, tous les chars passés à TrimEnd seront automatiquement regroupés dans un tableau.

0 votes

99 n'est pas suffisant pour une solution polyvalente. doubleValue.ToString("0." + new string('#', 339)) est sans perte. Comparez ces méthodes en utilisant la valeur double.Epsilon.

22voto

Paul Sasik Points 37766

Ceci est une solution d'analyse de chaîne où le nombre source (double) est converti en chaîne et analysé en ses composants constitutifs. Il est ensuite réassemblé par des règles en la représentation numérique de pleine longueur. Il prend également en compte la locale comme demandé.

Mise à jour : Les tests des conversions incluent uniquement des nombres entiers à un chiffre, ce qui est la norme, mais l'algorithme fonctionne également pour quelque chose comme : 239483.340901e-20

using System;
using System.Text;
using System.Globalization;
using System.Threading;

public class MyClass
{
    public static void Main()
    {
        Console.WriteLine(ToLongString(1.23e-2));            
        Console.WriteLine(ToLongString(1.234e-5));           // 0.00010234
        Console.WriteLine(ToLongString(1.2345E-10));         // 0.00000001002345
        Console.WriteLine(ToLongString(1.23456E-20));        // 0.00000000000000000100023456
        Console.WriteLine(ToLongString(5E-20));
        Console.WriteLine("");
        Console.WriteLine(ToLongString(1.23E+2));            // 123
        Console.WriteLine(ToLongString(1.234e5));            // 1023400
        Console.WriteLine(ToLongString(1.2345E10));          // 1002345000000
        Console.WriteLine(ToLongString(-7.576E-05));         // -0.00007576
        Console.WriteLine(ToLongString(1.23456e20));
        Console.WriteLine(ToLongString(5e+20));
        Console.WriteLine("");
        Console.WriteLine(ToLongString(9.1093822E-31));        // masse d'un électron
        Console.WriteLine(ToLongString(5.9736e24));            // masse de la terre 

        Console.ReadLine();
    }

    private static string ToLongString(double input)
    {
        string strOrig = input.ToString();
        string str = strOrig.ToUpper();

        // si la représentation en chaîne a été compactée à partir de la notation scientifique, il suffit de la renvoyer :
        if (!str.Contains("E")) return strOrig;

        bool negativeNumber = false;

        if (str[0] == '-')
        {
            str = str.Remove(0, 1);
            negativeNumber = true;
        }

        string sep = Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator;
        char decSeparator = sep.ToCharArray()[0];

        string[] exponentParts = str.Split('E');
        string[] decimalParts = exponentParts[0].Split(decSeparator);

        // corriger le manque de point décimal :
        if (decimalParts.Length==1) decimalParts = new string[]{exponentParts[0],"0"};

        int exponentValue = int.Parse(exponentParts[1]);

        string newNumber = decimalParts[0] + decimalParts[1];

        string result;

        if (exponentValue > 0)
        {
            result = 
                newNumber + 
                GetZeros(exponentValue - decimalParts[1].Length);
        }
        else // exposant négatif
        {
            result = 
                "0" + 
                decSeparator + 
                GetZeros(exponentValue + decimalParts[0].Length) + 
                newNumber;

            result = result.TrimEnd('0');
        }

        if (negativeNumber)
            result = "-" + result;

        return result;
    }

    private static string GetZeros(int zeroCount)
    {
        if (zeroCount < 0) 
            zeroCount = Math.Abs(zeroCount);

        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < zeroCount; i++) sb.Append("0");    

        return sb.ToString();
    }
}

0 votes

Huh. Honnêtement, j'ai remarqué qu'il avait été voté à la baisse donc je n'ai pas examiné le code très attentivement. Je viens de le lire et tu as raison. Ils sont similaires, j'ai simplement choisi de ne pas utiliser l'expression régulière dans mon processus et j'ai fait mon propre traitement de chaîne. As-tu testé cette solution? C'est une application console complète.

0 votes

Pas encore, je le ferai bientôt... ;)

3 votes

Celui-ci est plus facile à lire, car vous n'avez pas à comprendre le regex.

8voto

Lucero Points 38928

C'est ce que j'ai pour le moment, cela semble fonctionner, mais peut-être que quelqu'un a une meilleure solution :

private static readonly Regex rxScientific = new Regex(@"^(?-?)(?\d+)(\.(?\d*?)0*)?E(?[+\-]\d+)$", RegexOptions.IgnoreCase|RegexOptions.ExplicitCapture|RegexOptions.CultureInvariant);

public static string ToFloatingPointString(double value) {
    return ToFloatingPointString(value, NumberFormatInfo.CurrentInfo);
}

public static string ToFloatingPointString(double value, NumberFormatInfo formatInfo) {
    string result = value.ToString("r", NumberFormatInfo.InvariantInfo);
    Match match = rxScientific.Match(result);
    if (match.Success) {
        Debug.WriteLine("Format scientifique trouvé : {0} => [{1}] [{2}] [{3}] [{4}]", result, match.Groups["sign"], match.Groups["head"], match.Groups["tail"], match.Groups["exponent"]);
        int exponent = int.Parse(match.Groups["exponent"].Value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
        StringBuilder builder = new StringBuilder(result.Length+Math.Abs(exponent));
        builder.Append(match.Groups["sign"].Value);
        if (exponent >= 0) {
            builder.Append(match.Groups["head"].Value);
            string tail = match.Groups["tail"].Value;
            if (exponent < tail.Length) {
                builder.Append(tail, 0, exponent);
                builder.Append(formatInfo.NumberDecimalSeparator);
                builder.Append(tail, exponent, tail.Length-exponent);
            } else {
                builder.Append(tail);
                builder.Append('0', exponent-tail.Length);
            }
        } else {
            builder.Append('0');
            builder.Append(formatInfo.NumberDecimalSeparator);
            builder.Append('0', (-exponent)-1);
            builder.Append(match.Groups["head"].Value);
            builder.Append(match.Groups["tail"].Value);
        }
        result = builder.ToString();
    }
    return result;
}

// code de test
double x = 1.0;
for (int i = 0; i < 200; i++) {
    x /= 10;
}
Console.WriteLine(x);
Console.WriteLine(ToFloatingPointString(x));

0 votes

-1 car ne fournit pas de solution pour la situation suivante (et ne peut pas le faire) : double d1 = 1e-200; d = d + 1; ToFloatingPointString(d) renvoie simplement 1 ici. Pas 1,000...........000001.

5 votes

Ajouter un à un très petit double est juste votre idée, et n'a rien à voir avec la question en cours. Si vous l'exécutez sans le d=d+1, vous verrez qu'il affiche en fait 0,000.....0001.

0 votes

Trouvez un moyen de calculer 1e-200 lors de l'exécution au lieu de définir une valeur "constante", je voterai pour.

3voto

Ed Power Points 3179

À l'ancienne, quand nous devions écrire nos propres formateurs, nous isolions la mantisse et l'exposant et les formatrions séparément.

Dans cet article de Jon Skeet (https://csharpindepth.com/articles/FloatingPoint), il fournit un lien vers sa routine DoubleConverter.cs qui devrait faire exactement ce que vous voulez. Skeet fait également référence à cela à extraire la mantisse et l'exposant d'un double en c#.

0 votes

Merci pour le lien, j'ai déjà essayé le code de Jon, cependant pour mon but c'est un peu trop précis ; par exemple, 0.1 ne s'affiche pas comme 0.1 (ce qui est techniquement correct, mais pas ce dont j'aurais besoin)...

0 votes

Oui, mais tu vois, le but de Jon avec son code est d'afficher le nombre EXACTEMENT et c'est un peu trop pour mon cas. L'arrondi effectué par le programme lors de ToString() me convient parfaitement, c'est probablement aussi pourquoi la plupart des solutions proposées ici utilisent ToString() comme base pour un traitement ultérieur.

0 votes

Bonjour! Je viens d'arriver ici depuis 10 ans dans le futur pour vous informer que le lien hypertexte vers l'article de Jon est brisé.

3voto

Brian Points 14040

La solution basée sur les logarithmes obligatoires. Notez que cette solution, car elle implique des calculs mathématiques, peut réduire légèrement la précision de votre nombre. Pas fortement testé.

private static string DoubleToLongString(double x)
{
    int shift = (int)Math.Log10(x);
    if (Math.Abs(shift) <= 2)
    {
        return x.ToString();
    }

    if (shift < 0)
    {
        double y = x * Math.Pow(10, -shift);
        return "0.".PadRight(-shift + 2, '0') + y.ToString().Substring(2);
    }
    else
    {
        double y = x * Math.Pow(10, 2 - shift);
        return y + "".PadRight(shift - 2, '0');
    }
}

Éditer: Si le point décimal traverse la partie non nulle du nombre, cet algorithme échouera misérablement. J'ai essayé d'être simple et j'ai été trop loin.

0 votes

Merci pour votre contribution, je vais essayer de mettre en œuvre une solution entièrement fonctionnelle comme celle-ci et la comparer à la mienne.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X