Dubbele naar string conversie zonder wetenschappelijke notatie

Hoe converteer je een double naar een drijvende-komma-tekenreeksweergave zonder wetenschappelijke notatie in het .NET Framework?

“Kleine” voorbeelden (effectieve getallen kunnen elke grootte hebben, zoals 1.5E200of 1e-200) :

3248971234698200000000000000000000000000000000
0.00000000000000000000000000000000000023897356978234562

Geen van de standaard getalnotatiesis zo, en een aangepaste indelinglijkt ook geen open aantal cijfers toe te staan na het decimaalteken.

Dit is geen duplicaat van Hoe dubbel naar string te converteren zonder de macht naar 10 representatie (E-05)omdat de antwoorden die daar gegeven worden niethet probleem oplossen. De geaccepteerde oplossing in deze vraag was om een ​​vast punt te gebruiken (zoals 20 cijfers), wat niet is wat ik wil. Een opmaak met een vast punt en het trimmen van de overbodige 0 lost het probleem ook niet op, omdat de maximale breedte voor een vaste breedte 99 tekens is.

Opmerking:de oplossing moet correct omgaan met aangepaste getalnotaties (bijv. ander decimaalteken, afhankelijk van cultuurinformatie).

Bewerken:de vraag gaat eigenlijk alleen over het weerleggen van bovengenoemde getallen. Ik weet hoe drijvende-kommagetallen werken en welke getallen ermee kunnen worden gebruikt en berekend.


Antwoord 1, autoriteit 100%

Voor een algemene oplossing¹ moet u 339 plaatsen behouden:

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

Het maximum aantal decimale cijfers die niet nul is, is 16. 15 staan ​​aan de rechterkant van de komma. De exponent kan die 15 cijfers maximaal 324 plaatsen naar rechts opschuiven. (Bekijk het bereik en de precisie.)

Het werkt voor double.Epsilon, double.MinValue, double.MaxValueen alles daartussenin.

De prestatie zal veel beter zijn dan de regex/string manipulatie-oplossingen, aangezien al het formatterings- en stringwerk in één keer wordt gedaan door onbeheerde CLR-code. De code is ook veel eenvoudiger om te bewijzen dat deze correct is.

Voor gebruiksgemak en nog betere prestaties, maak er een constante van:

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

¹ Update:ik heb ten onrechte gezegd dat dit ook een oplossing zonder verlies was. In feite is dat niet zo, aangezien ToStringzijn normale weergave-afronding doet voor alle formaten behalve r. live voorbeeld. Thanks, @Verachting! Raadpleeg Lothing’s antwoordals je de mogelijkheid nodig hebt om in vaste-puntnotatie heen en weer te reizen (dat wil zeggen, als je .ToString("r")vandaag).


Antwoord 2, autoriteit 72%

Ik had een soortgelijk probleem en dit werkte voor mij:

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

F99 is misschien overdreven, maar je snapt het idee.


Antwoord 3, autoriteit 46%

Dit is een oplossing voor het parseren van strings waarbij het bronnummer (dubbel) wordt omgezet in een string en geparseerd in de samenstellende componenten. Het wordt vervolgens opnieuw samengesteld door regels in de volledige numerieke weergave. Het houdt ook rekening met de locale zoals gevraagd.

Update: de tests van de conversies bevatten alleen gehele getallen van één cijfer, wat de norm is, maar het algoritme werkt ook voor zoiets als: 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();
    }
}

Antwoord 4, autoriteit 41%

Je zou de doublenaar decimalkunnen casten en dan ToString()kunnen doen.

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

Ik heb geen prestatietests gedaan die sneller zijn, met casten van 64-bits doublenaar 128-bits decimalof een opmaakreeks van meer dan 300 tekens. Oh, en er kunnen mogelijk overloopfouten optreden tijdens de conversie, maar als uw waarden op een decimalpassen, zou dit goed moeten werken.

Update:het casten lijkt een stuk sneller te gaan. Met behulp van een voorbereide format-string zoals gegeven in het andere antwoord, duurt het formatteren van een miljoen keer 2,3 seconden en casten slechts 0,19 seconden. Herhaalbaar. Dat is 10x sneller. Nu gaat het alleen nog om het waardebereik.


Antwoord 5, autoriteit 17%

Dit is wat ik tot nu toe heb, lijkt te werken, maar misschien heeft iemand een betere oplossing:

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

Antwoord 6, autoriteit 13%

Het probleem bij het gebruik van #.###...###of F99is dat de precisie op de laatste decimalen niet behouden blijft, bijvoorbeeld:

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

Het probleem met DecimalConverter.csis dat het traag is. Deze code is hetzelfde idee als het antwoord van Sasik, maar twee keer zo snel. Eenheidstestmethode onderaan.

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

Antwoord 7, autoriteit 7%

De verplichte op logaritme gebaseerde oplossing. Merk op dat deze oplossing, omdat er wiskunde bij komt kijken, de nauwkeurigheid van je getal een beetje kan verminderen. Niet zwaar getest.

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

Bewerken: Als de komma een niet-nul deel van het getal kruist, zal dit algoritme jammerlijk falen. Ik probeerde het simpel en ging te ver.


Antwoord 8, autoriteit 7%

Vroeger moesten we onze eigen formatters schrijven, we isoleren de mantisse en exponent en formatteren ze afzonderlijk.

In dit artikel van Jon Skeet (https://csharpindepth.com/articles/FloatingPoint) hij geeft een link naar zijn DoubleConverter.cs-routine die precies zou moeten doen wat je wilt. Skeet verwijst hier ook naar bij mantisse en exponent extraheren uit dubbel in c#.


Antwoord 9, autoriteit 4%

Ik heb zojuist geïmproviseerd op de bovenstaande code om deze te laten werken voor negatieve exponentiële waarden.

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

Deze code neemt een invoermap en converteert op basis van het scheidingsteken alle waarden in wetenschappelijke notatie naar numeriek formaat.

Bedankt


Antwoord 10, autoriteit 2%

probeer deze:

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

Antwoord 11

Omdat we miljoenen programmeurs over de hele wereld zijn, is het altijd een goede gewoonte om te proberen te zoeken als iemand uw probleem al is tegengekomen. Soms zijn er oplossingen die rommel zijn, wat betekent dat het tijd is om je eigen oplossingen te schrijven, en soms zijn er geweldige, zoals de volgende:

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

(details: http://www.yoda.arachsys.com/csharp/ drijvende-komma.html)


Antwoord 12

string strdScaleFactor = dScaleFactor.ToString(); // where dScaleFactor = 3.531467E-05
decimal decimalScaleFactor = Decimal.Parse(strdScaleFactor, System.Globalization.NumberStyles.Float);

13

Ik kan het mis hebben, maar is het niet zo?

data.ToString("n");

http://msdn.microsoft.com/en-us/library /dwhawy9k.aspx


14

Ik denk dat je hoeft alleen maar om het gebruik iFormat met

ToString(doubleVar, System.Globalization.NumberStyles.Number)

Bijvoorbeeld:

double d = double.MaxValue;
string s = d.ToString(d, System.Globalization.NumberStyles.Number);

15

Deze oplossing werd met behulp van de aangepaste formaten.
probeer dit:

double d;
d = 1234.12341234;
d.ToString("#########0.#########");

16

Gewoon om te bouwen op wat jcasso gezegd wat je kunt doen is om je dubbele waarde aan te passen door het veranderen van de exponent, zodat uw favoriete formaat het voor u zou doen, de opmaak toepassen, en dan het resultaat opgevuld met nullen te compenseren voor de afstelling.


17

Dit werkt prima voor mij …

double number = 1.5E+200;
string s = number.ToString("#");
//Output: "150000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"

Other episodes