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.
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).
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.
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();
}
}
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.
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));
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 .
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.
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
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();
}
}
}
}
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;
}
string strdScaleFactor = dScaleFactor.ToString(); // where dScaleFactor = 3.531467E-05
decimal decimalScaleFactor = Decimal.Parse(strdScaleFactor, System.Globalization.NumberStyles.Float);
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 )