web-dev-qa-db-ger.com

Double-to-String-Konvertierung ohne wissenschaftliche Notation

Wie konvertiert man ein Double in eine Gleitkomma-String-Darstellung ohne wissenschaftliche Notation in .NET Framework?

"Kleine" Stichproben (effektive Zahlen können von beliebiger Größe sein, z. B. 1.5E200 oder 1e-200):

3248971234698200000000000000000000000000000000
0.00000000000000000000000000000000000023897356978234562

Keines der Standard-Zahlenformate ist so, und ein benutzerdefiniertes Format scheint auch keine offene Anzahl von Ziffern nach dem Dezimaltrennzeichen zuzulassen.

Dies ist kein Duplikat von Konvertieren von double in string ohne die Potenz zu 10-Darstellung (E-05) , da die dort gegebenen Antworten nicht Lösen Sie das Problem zur Hand. Die akzeptierte Lösung für diese Frage war die Verwendung eines festen Punkts (z. B. 20 Stellen), was ich nicht möchte. Eine Festkomma-Formatierung und das Trimmen der redundanten 0 lösen das Problem ebenfalls nicht, da die maximale Breite für die feste Breite 99 Zeichen beträgt.

Hinweis: Die Lösung muss mit benutzerdefinierten Zahlenformaten (z. B. anderen Dezimaltrennzeichen, abhängig von den Kulturinformationen) korrekt umgehen.

Edit: Bei der Frage geht es eigentlich nur um das Verschieben der oben genannten Zahlen. Mir ist bekannt, wie Gleitkommazahlen funktionieren und welche Zahlen mit ihnen verwendet und berechnet werden können.

68
Lucero

Für eine Allzwecklösung¹ müssen Sie 339 Stellen erhalten:

doubleValue.ToString("0." + new string('#', 339))

Die maximale Anzahl von Dezimalstellen ungleich Null beträgt 16. Auf der rechten Seite des Dezimalpunkts befinden sich 15 Stellen. Der Exponent kann diese 15 Ziffern um maximal 324 Stellen nach rechts verschieben. ( Siehe Reichweite und Präzision. )

Es funktioniert für double.Epsilon , double.MinValue , double.MaxValue und alles dazwischen.

Die Leistung ist viel höher als bei den Lösungen zur Manipulation von Regex/Zeichenfolgen, da alle Formatierungs- und Zeichenfolgenarbeiten in einem Durchgang mit nicht verwaltetem CLR-Code ausgeführt werden. Außerdem ist der Code viel einfacher zu korrigieren.

Machen Sie es zu einer Konstanten, um die Bedienung zu vereinfachen und die Leistung zu verbessern:

public static class FormatStrings
{
    public const string DoubleFixedPoint = "0.###################################################################################################################################################################################################################################################################################################################################################";
}

¹ Update: Ich habe fälschlicherweise gesagt, dass dies auch eine verlustfreie Lösung ist. Tatsächlich ist dies nicht der Fall, da ToString seine normale Anzeigerundung für alle Formate außer r ausführt. Live-Beispiel. Danke, @Loathing! Bitte lesen Sie Lothings Antwort , wenn Sie die Möglichkeit benötigen, in Festkommanotation zu runden (d. H. Wenn Sie heute .ToString("r") verwenden).

28
jnm2

Ich hatte ein ähnliches Problem und das hat bei mir funktioniert:

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

F99 mag übertrieben sein, aber Sie haben die Idee.

28
Robert Lamm

Hierbei handelt es sich um eine String-Parsing-Lösung, bei der die Quellennummer (double) in einen String umgewandelt und in ihre Bestandteile zerlegt wird. Es wird dann durch Regeln in die numerische Darstellung in voller Länge wieder zusammengesetzt. Es berücksichtigt auch das Gebietsschema, wie angefordert.

pdate: Die Tests der Konvertierungen enthalten nur einstellige ganze Zahlen, was die Norm ist, aber der Algorithmus funktioniert auch für so etwas wie: 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));        // mass of an electron
        Console.WriteLine(ToLongString(5.9736e24));            // mass of the earth 

        Console.ReadLine();
    }

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

        // if string representation was collapsed from scientific notation, just return it:
        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);

        // fix missing decimal point:
        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 // negative exponent
        {
            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();
    }
}
18
Paul Sasik

Sie können double in decimal umwandeln und dann ToString() ausführen.

(0.000000005).ToString()   // 5E-09
((decimal)(0.000000005)).ToString()   // 0,000000005

Ich habe keine Leistungstests durchgeführt, die schneller sind und von 64-Bit double auf 128-Bit decimal oder eine Formatzeichenfolge mit mehr als 300 Zeichen übertragen. Oh, und während der Konvertierung kann es möglicherweise zu Überlauffehlern kommen, aber wenn Ihre Werte zu decimal passen, sollte dies problemlos funktionieren.

pdate: Das Casting scheint viel schneller zu sein. Wenn Sie eine vorbereitete Formatzeichenfolge wie in der anderen Antwort angegeben verwenden, dauert das millionenfache Formatieren 2,3 Sekunden und das Wirken nur 0,19 Sekunden. Wiederholbar. Das ist 10x schneller. Jetzt geht es nur noch um den Wertebereich.

11
ygoe

Das ist, was ich bisher habe, scheint zu funktionieren, aber vielleicht hat jemand eine bessere Lösung:

private static readonly Regex rxScientific = new Regex(@"^(?<sign>-?)(?<head>\d+)(\.(?<tail>\d*?)0*)?E(?<exponent>[+\-]\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("Found scientific format: {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;
}

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

Früher mussten wir unsere eigenen Formatierer schreiben, um die Mantisse und den Exponenten zu isolieren und sie getrennt zu formatieren.

In diesem Artikel von Jon Skeet ( https://csharpindepth.com/articles/FloatingPoint ) stellt er einen Link zu seiner DoubleConverter.cs-Routine bereit, die genau das tun sollte, was Sie wollen. Skeet bezieht sich auch auf Mantisse und Exponent werden aus double in c # extrahiert .

3
Ed Power

Die obligatorische logarithmische Lösung. Beachten Sie, dass diese Lösung die Genauigkeit Ihrer Zahl ein wenig verringern kann, da sie das Ausführen von Mathematik umfasst. Nicht stark getestet.

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

Edit: Wenn der Dezimalpunkt einen Teil der Zahl ungleich Null kreuzt, schlägt dieser Algorithmus kläglich fehl. Ich habe es einfach versucht und bin zu weit gegangen.

2
Brian

Ich habe gerade den obigen Code improvisiert, damit er für negative Exponentialwerte funktioniert.

using System;
using System.Text.RegularExpressions;
using System.IO;
using System.Text;
using System.Threading;

namespace ConvertNumbersInScientificNotationToPlainNumbers
{
    class Program
    {
        private static string ToLongString(double input)
        {
            string str = input.ToString(System.Globalization.CultureInfo.InvariantCulture);

            // if string representation was collapsed from scientific notation, just return it:
            if (!str.Contains("E")) return str;

            var positive = true;
            if (input < 0)
            {
                positive = false;
            }

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

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

            // fix missing decimal point:
            if (decimalParts.Length == 1) decimalParts = new string[] { exponentParts[0], "0" };

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

            string newNumber = decimalParts[0].Replace("-", "").
                Replace("+", "") + decimalParts[1];

            string result;

            if (exponentValue > 0)
            {
                if (positive)
                    result =
                        newNumber +
                        GetZeros(exponentValue - decimalParts[1].Length);
                else

                    result = "-" +
                     newNumber +
                     GetZeros(exponentValue - decimalParts[1].Length);


            }
            else // negative exponent
            {
                if (positive)
                    result =
                        "0" +
                        decSeparator +
                        GetZeros(exponentValue + decimalParts[0].Replace("-", "").
                                   Replace("+", "").Length) + newNumber;
                else
                    result =
                    "-0" +
                    decSeparator +
                    GetZeros(exponentValue + decimalParts[0].Replace("-", "").
                             Replace("+", "").Length) + newNumber;

                result = result.TrimEnd('0');
            }
            float temp = 0.00F;

            if (float.TryParse(result, out temp))
            {
                return result;
            }
            throw new Exception();
        }

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

        public static void Main(string[] args)
        {
            //Get Input Directory.
            Console.WriteLine(@"Enter the Input Directory");
            var readLine = Console.ReadLine();
            if (readLine == null)
            {
                Console.WriteLine(@"Enter the input path properly.");
                return;
            }
            var pathToInputDirectory = readLine.Trim();

            //Get Output Directory.
            Console.WriteLine(@"Enter the Output Directory");
            readLine = Console.ReadLine();
            if (readLine == null)
            {
                Console.WriteLine(@"Enter the output path properly.");
                return;
            }
            var pathToOutputDirectory = readLine.Trim();

            //Get Delimiter.
            Console.WriteLine("Enter the delimiter;");
            var columnDelimiter = (char)Console.Read();

            //Loop over all files in the directory.
            foreach (var inputFileName in Directory.GetFiles(pathToInputDirectory))
            {
                var outputFileWithouthNumbersInScientificNotation = string.Empty;
                Console.WriteLine("Started operation on File : " + inputFileName);

                if (File.Exists(inputFileName))
                {
                    // Read the file
                    using (var file = new StreamReader(inputFileName))
                    {
                        string line;
                        while ((line = file.ReadLine()) != null)
                        {
                            String[] columns = line.Split(columnDelimiter);
                            var duplicateLine = string.Empty;
                            int lengthOfColumns = columns.Length;
                            int counter = 1;
                            foreach (var column in columns)
                            {
                                var columnDuplicate = column;
                                try
                                {
                                    if (Regex.IsMatch(columnDuplicate.Trim(),
                                                      @"^[+-]?[0-9]+(\.[0-9]+)?[E]([+-]?[0-9]+)$",
                                                      RegexOptions.IgnoreCase))
                                    {
                                        Console.WriteLine("Regular expression matched for this :" + column);

                                        columnDuplicate = ToLongString(Double.Parse
                                                                           (column,
                                                                            System.Globalization.NumberStyles.Float));

                                        Console.WriteLine("Converted this no in scientific notation " +
                                                          "" + column + "  to this number " +
                                                          columnDuplicate);
                                    }
                                }
                                catch (Exception)
                                {

                                }
                                duplicateLine = duplicateLine + columnDuplicate;

                                if (counter != lengthOfColumns)
                                {
                                    duplicateLine = duplicateLine + columnDelimiter.ToString();
                                }
                                counter++;
                            }
                            duplicateLine = duplicateLine + Environment.NewLine;
                            outputFileWithouthNumbersInScientificNotation = outputFileWithouthNumbersInScientificNotation + duplicateLine;
                        }

                        file.Close();
                    }

                    var outputFilePathWithoutNumbersInScientificNotation
                        = Path.Combine(pathToOutputDirectory, Path.GetFileName(inputFileName));

                    //Create Directory If it does not exist.
                    if (!Directory.Exists(pathToOutputDirectory))
                        Directory.CreateDirectory(pathToOutputDirectory);

                    using (var outputFile =
                        new StreamWriter(outputFilePathWithoutNumbersInScientificNotation))
                    {
                        outputFile.Write(outputFileWithouthNumbersInScientificNotation);
                        outputFile.Close();
                    }

                    Console.WriteLine("The transformed file is here :" +
                        outputFilePathWithoutNumbersInScientificNotation);
                }
            }
        }
    }
}

Dieser Code verwendet ein Eingabeverzeichnis und konvertiert basierend auf dem Begrenzer alle Werte in wissenschaftlicher Notation in ein numerisches Format.

Vielen Dank

2
Egalitarian

Das Problem mit #.###...### oder F99 ist, dass die Genauigkeit bei den letzten Dezimalstellen nicht erhalten bleibt, z. B .:

String t1 = (0.0001/7).ToString("0." + new string('#', 339)); // 0.0000142857142857143
String t2 = (0.0001/7).ToString("r");                         //      1.4285714285714287E-05

Das Problem mit DecimalConverter.cs ist, dass es langsam ist. Dieser Code entspricht der Antwort von Sasik, ist jedoch doppelt so schnell. Unit-Test-Methode unten.

public static class RoundTrip {

    private static String[] zeros = new String[1000];

    static RoundTrip() {
        for (int i = 0; i < zeros.Length; i++) {
            zeros[i] = new String('0', i);
        }
    }

    private static String ToRoundTrip(double value) {
        String str = value.ToString("r");
        int x = str.IndexOf('E');
        if (x < 0) return str;

        int x1 = x + 1;
        String exp = str.Substring(x1, str.Length - x1);
        int e = int.Parse(exp);

        String s = null;
        int numDecimals = 0;
        if (value < 0) {
            int len = x - 3;
            if (e >= 0) {
                if (len > 0) {
                    s = str.Substring(0, 2) + str.Substring(3, len);
                    numDecimals = len;
                }
                else
                    s = str.Substring(0, 2);
            }
            else {
                // remove the leading minus sign
                if (len > 0) {
                    s = str.Substring(1, 1) + str.Substring(3, len);
                    numDecimals = len;
                }
                else
                    s = str.Substring(1, 1);
            }
        }
        else {
            int len = x - 2;
            if (len > 0) {
                s = str[0] + str.Substring(2, len);
                numDecimals = len;
            }
            else
                s = str[0].ToString();
        }

        if (e >= 0) {
            e = e - numDecimals;
            String z = (e < zeros.Length ? zeros[e] : new String('0', e));
            s = s + z;
        }
        else {
            e = (-e - 1);
            String z = (e < zeros.Length ? zeros[e] : new String('0', e));
            if (value < 0)
                s = "-0." + z + s;
            else
                s = "0." + z + s;
        }

        return s;
    }

    private static void RoundTripUnitTest() {
        StringBuilder sb33 = new StringBuilder();
        double[] values = new [] { 123450000000000000.0, 1.0 / 7, 10000000000.0/7, 100000000000000000.0/7, 0.001/7, 0.0001/7, 100000000000000000.0, 0.00000000001,
         1.23e-2, 1.234e-5, 1.2345E-10, 1.23456E-20, 5E-20, 1.23E+2, 1.234e5, 1.2345E10, -7.576E-05, 1.23456e20, 5e+20, 9.1093822E-31, 5.9736e24, double.Epsilon };

        foreach (int sign in new [] { 1, -1 }) {
            foreach (double val in values) {
                double val2 = sign * val;
                String s1 = val2.ToString("r");
                String s2 = ToRoundTrip(val2);

                double val2_ = double.Parse(s2);
                double diff = Math.Abs(val2 - val2_);
                if (diff != 0) {
                    throw new Exception("Value {0} did not pass ToRoundTrip.".Format2(val.ToString("r")));
                }
                sb33.AppendLine(s1);
                sb33.AppendLine(s2);
                sb33.AppendLine();
            }
        }
    }
}
2
Loathing

probier diese:

public static string DoubleToFullString(double value, 
                                        NumberFormatInfo formatInfo)
{
    string[] valueExpSplit;
    string result, decimalSeparator;
    int indexOfDecimalSeparator, exp;

    valueExpSplit = value.ToString("r", formatInfo)
                         .ToUpper()
                         .Split(new char[] { 'E' });

    if (valueExpSplit.Length > 1)
    {
        result = valueExpSplit[0];
        exp = int.Parse(valueExpSplit[1]);
        decimalSeparator = formatInfo.NumberDecimalSeparator;

        if ((indexOfDecimalSeparator 
             = valueExpSplit[0].IndexOf(decimalSeparator)) > -1)
        {
            exp -= (result.Length - indexOfDecimalSeparator - 1);
            result = result.Replace(decimalSeparator, "");
        }

        if (exp >= 0) result += new string('0', Math.Abs(exp));
        else
        {
            exp = Math.Abs(exp);
            if (exp >= result.Length)
            {
                result = "0." + new string('0', exp - result.Length) 
                             + result;
            }
            else
            {
                result = result.Insert(result.Length - exp, decimalSeparator);
            }
        }
    }
    else result = valueExpSplit[0];

    return result;
}
1
manji
string strdScaleFactor = dScaleFactor.ToString(); // where dScaleFactor = 3.531467E-05

decimal decimalScaleFactor = Decimal.Parse(strdScaleFactor, System.Globalization.NumberStyles.Float);
0
Priya

Als Millionen von Programmierern auf der ganzen Welt ist es immer eine gute Praxis, die Suche zu versuchen, wenn jemand bereits auf Ihr Problem gestoßen ist. Manchmal gibt es Lösungen, die Müll sind, was bedeutet, dass es Zeit ist, eigene Lösungen zu schreiben, und manchmal gibt es großartige Lösungen, wie zum Beispiel die folgenden:

http://www.yoda.arachsys.com/csharp/DoubleConverter.cs

(Details: http://www.yoda.arachsys.com/csharp/floatingpoint.html )

0
Letterman