Wie konvertiere ich ein Double in eine Gleitkomma-String-Darstellung ohne wissenschaftliche Notation in .NET Framework?
"Kleine" Stichproben (effektive Zahlen können beliebig groß sein, z. B. 1.5E200
oder 1e-200
):
3248971234698200000000000000000000000000000000
0.00000000000000000000000000000000000023897356978234562
Keines der Standardnummernformate 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 Darstellung der Potenz in 10 (E-05), da die dort gegebenen Antworten das vorliegende Problem nicht lösen. Die akzeptierte Lösung in dieser Frage bestand darin, einen festen Punkt (z. B. 20 Stellen) zu verwenden, was ich nicht möchte. Eine Fixpunktformatierung 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 korrekt mit benutzerdefinierten Zahlenformaten umgehen (z. B. andere Dezimaltrennzeichen, abhängig von den Kulturinformationen).
Bearbeiten: Bei der Frage geht es eigentlich nur darum, die oben genannten Zahlen zu verschieben. Mir ist bewusst, wie Gleitkommazahlen funktionieren und welche Zahlen damit verwendet und berechnet werden können.
quelle
Antworten:
Für eine universelle Lösung müssen 339 Stellen erhalten bleiben:
doubleValue.ToString("0." + new string('#', 339))
Die maximale Anzahl von Dezimalstellen ungleich Null beträgt 16. 15 befinden sich auf der rechten Seite des Dezimalpunkts. 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 , was dazwischen liegt .Die Leistung ist viel höher als bei den Regex- / String-Manipulationslösungen, da alle Formatierungs- und String-Arbeiten in einem Durchgang von nicht verwaltetem CLR-Code ausgeführt werden. Außerdem ist der Code viel einfacher zu beweisen.
Machen Sie es für eine einfache Bedienung und noch bessere Leistung zu einer Konstanten:
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
ToString
Fall , da die normale Anzeige für alle Formate gerundet wird, außerr
. Live Beispiel. Danke, @Loathing! Bitte lesen Sie die Antwort von Lothing, wenn Sie die Möglichkeit benötigen, in Fixpunktnotation zu runden (dh wenn Sie.ToString("r")
heute verwenden).quelle
String t1 = (0.0001/7).ToString("0." + new string('#', 339)); // 0.0000142857142857143
versus: DieString t2 = (0.0001/7).ToString("r"); // 1.4285714285714287E-05
Präzision geht an den letzten Dezimalstellen verloren.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.
quelle
TrimEnd('0')
ist ausreichend, weil daschar
Array istparams
. Das heißt, alle übergebenenchar
sTrimEnd
werden automatisch in einem Array gruppiert.doubleValue.ToString("0." + new string('#', 339))
ist verlustfrei. Vergleichen Sie diese Methoden mit dem Wertdouble.Epsilon
.Dies ist eine String-Parsing-Lösung, bei der die Quellennummer (double) in einen String konvertiert und in ihre Bestandteile analysiert wird. Es wird dann durch Regeln wieder zu einer numerischen Darstellung in voller Länge zusammengesetzt. Das angeforderte Gebietsschema wird ebenfalls berücksichtigt.
Update : Die Tests der Konvertierungen enthalten nur einstellige ganze Zahlen, was die Norm ist, aber der Algorithmus funktioniert auch für Folgendes: 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(); } }
quelle
Sie könnten das
double
zudecimal
und dann tunToString()
.(0.000000005).ToString() // 5E-09 ((decimal)(0.000000005)).ToString() // 0,000000005
Ich habe keine schnelleren Leistungstests durchgeführt, die von 64-Bit
double
auf 128-Bitdecimal
oder eine Formatzeichenfolge mit über 300 Zeichen umgewandelt wurden. Oh, und es kann möglicherweise zu Überlauffehlern während der Konvertierung kommen, aber wenn Ihre Werte zu a passen,decimal
sollte dies gut funktionieren.Update: Das Casting scheint viel schneller zu sein. Bei Verwendung einer vorbereiteten Formatzeichenfolge wie in der anderen Antwort angegeben dauert das millionenfache Formatieren 2,3 Sekunden und das Umwandeln nur 0,19 Sekunden. Wiederholbar. Das ist 10x schneller . Jetzt geht es nur noch um den Wertebereich.
quelle
((decimal)(1e-200)).ToString()
zum Beispiel gibt zurück,0
was falsch ist.double.ToString("0.############################")
. Nach meinem Test ist Ihr nur 3x schneller. In1e-28
beiden Fällen ist dies nur dann eine gültige Antwort, wenn Sie sicher sind, dass Sie keine Ziffern unten drucken müssen und Ihr Double nicht groß ist. Beides sind keine Einschränkungen in der ursprünglichen Frage.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));
quelle
double x = 1.0; for (int i = 0; i < 200; i++) x /= 10; Console.WriteLine(x);
Das Problem bei der Verwendung von
#.###...###
oderF99
besteht darin, dass die Genauigkeit an den Enddezimalstellen nicht erhalten bleibt, z.String t1 = (0.0001/7).ToString("0." + new string('#', 339)); // 0.0000142857142857143 String t2 = (0.0001/7).ToString("r"); // 1.4285714285714287E-05
Das Problem dabei
DecimalConverter.cs
ist, dass es langsam ist. Dieser Code ist die gleiche Idee wie Sasiks Antwort, aber 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(); } } } }
quelle
Die obligatorische logarithmusbasierte Lösung. Beachten Sie, dass diese Lösung die Genauigkeit Ihrer Zahl ein wenig verringern kann, da sie Mathematik erfordert. 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'); } }
Bearbeiten: Wenn der Dezimalpunkt einen Teil der Zahl ungleich Null überschreitet, schlägt dieser Algorithmus kläglich fehl. Ich habe es einfach versucht und bin zu weit gegangen.
quelle
Früher, als wir unsere eigenen Formatierer schreiben mussten, haben wir die Mantisse und den Exponenten isoliert und separat formatiert.
In diesem Artikel von Jon Skeet ( https://csharpindepth.com/articles/FloatingPoint ) stellt er einen Link zu seiner Routine DoubleConverter.cs bereit, der genau das tun soll, was Sie wollen. Skeet bezieht sich auch darauf, wenn Mantisse und Exponent in c # aus double extrahiert werden .
quelle
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 nimmt ein Eingabeverzeichnis und konvertiert basierend auf dem Trennzeichen alle Werte in wissenschaftlicher Notation in ein numerisches Format.
Vielen Dank
quelle
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; }
quelle
Als Millionen von Programmierern weltweit 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 zu schreiben, und manchmal gibt es großartige, wie die folgenden:
http://www.yoda.arachsys.com/csharp/DoubleConverter.cs
(Details: http://www.yoda.arachsys.com/csharp/floatingpoint.html )
quelle
string strdScaleFactor = dScaleFactor.ToString(); // where dScaleFactor = 3.531467E-05 decimal decimalScaleFactor = Decimal.Parse(strdScaleFactor, System.Globalization.NumberStyles.Float);
quelle
Ich könnte mich irren, aber ist es nicht so?
data.ToString("n");
http://msdn.microsoft.com/en-us/library/dwhawy9k.aspx
quelle
Um auf dem aufzubauen, was jcasso gesagt hat, können Sie Ihren Doppelwert anpassen, indem Sie den Exponenten so ändern, dass Ihr Lieblingsformat dies für Sie erledigt, das Format anwenden und dann das Ergebnis mit Nullen auffüllen, um die Anpassung zu kompensieren.
quelle
Ich denke, Sie müssen nur IFormat mit verwenden
Beispiel:
double d = double.MaxValue; string s = d.ToString(d, System.Globalization.NumberStyles.Number);
quelle
Meine Lösung bestand darin, die benutzerdefinierten Formate zu verwenden. Versuche dies:
double d; d = 1234.12341234; d.ToString("#########0.#########");
quelle
d = 1.5E200
undd = 1E-200
. Die resultierende Zeichenfolge sollte fast 2000
Zeichen enthalten, sonst funktioniert Ihre Lösung nicht.doubleValue.ToString("0." + new string('#', 339))
ist verlustfrei. Vergleichen Sie diese Methoden mit dem Wertdouble.Epsilon
.Das funktioniert gut für mich ...
double number = 1.5E+200; string s = number.ToString("#"); //Output: "150000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
quelle
1.5e-200
.