¿Cómo convertir un doble en una representación de cadena de punto flotante sin notación científica en .NET Framework?
Muestras "pequeñas" (los números efectivos pueden ser de cualquier tamaño, como 1.5E200
o 1e-200
):
3248971234698200000000000000000000000000000000
0.00000000000000000000000000000000000023897356978234562
Ninguno de los formatos de números estándar es así, y un formato personalizado tampoco parece permitir tener un número abierto de dígitos después del separador decimal.
Esto no es un duplicado de Cómo convertir doble en cadena sin la representación de potencia a 10 (E-05) porque las respuestas que se dan allí no resuelven el problema en cuestión. La solución aceptada en esta pregunta fue usar un punto fijo (como 20 dígitos), que no es lo que quiero. Un formato de punto fijo y recortar el 0 redundante tampoco resuelve el problema porque el ancho máximo para el ancho fijo es de 99 caracteres.
Nota: la solución tiene que tratar correctamente con formatos numéricos personalizados (por ejemplo, otro separador decimal, dependiendo de la información cultural).
Editar: La pregunta es realmente solo sobre mostrar los números mencionados anteriormente. Soy consciente de cómo funcionan los números de punto flotante y qué números se pueden usar y calcular con ellos.
fuente
Respuestas:
Para una solución de uso general¹, debe conservar 339 lugares:
doubleValue.ToString("0." + new string('#', 339))
El número máximo de dígitos decimales distintos de cero es 16. Hay 15 en el lado derecho del punto decimal. El exponente puede mover esos 15 dígitos un máximo de 324 lugares a la derecha. ( Vea el rango y la precisión ) .
Funciona para
double.Epsilon
,double.MinValue
,double.MaxValue
, y nada en el medio.El rendimiento será mucho mayor que el de las soluciones de manipulación de expresiones regulares / cadenas, ya que todo el trabajo de formato y cadena se realiza en una sola pasada mediante código CLR no administrado. Además, el código es mucho más sencillo de demostrar que es correcto.
Para facilitar el uso y un rendimiento aún mejor, conviértalo en una constante:
public static class FormatStrings { public const string DoubleFixedPoint = "0.###################################################################################################################################################################################################################################################################################################################################################"; }
¹ Actualización: dije erróneamente que esta también era una solución sin pérdidas. De hecho no lo es, ya
ToString
que su visualización normal se redondea para todos los formatos exceptor
. Ejemplo vivo. ¡Gracias, @Loathing! Consulte la respuesta de Lothing si necesita la capacidad de realizar un viaje de ida y vuelta en notación de punto fijo (es decir, si está usando.ToString("r")
hoy).fuente
String t1 = (0.0001/7).ToString("0." + new string('#', 339)); // 0.0000142857142857143
versus: LaString t2 = (0.0001/7).ToString("r"); // 1.4285714285714287E-05
precisión se pierde en los lugares decimales finales.Tuve un problema similar y esto funcionó para mí:
doubleValue.ToString("F99").TrimEnd('0')
F99 puede ser exagerado, pero entiendes la idea.
fuente
TrimEnd('0')
es suficiente, porque lachar
matriz esparams
. Es decir, cualquierchar
correoTrimEnd
electrónico que se pase a se agrupará automáticamente en una matriz.doubleValue.ToString("0." + new string('#', 339))
no tiene pérdidas. Compare estos métodos usando el valordouble.Epsilon
.Esta es una solución de análisis de cadenas en la que el número de origen (doble) se convierte en una cadena y se analiza en sus componentes constituyentes. Luego, se vuelve a ensamblar mediante reglas en la representación numérica completa. También tiene en cuenta la configuración regional solicitada.
Actualización : Las pruebas de las conversiones solo incluyen números enteros de un solo dígito, que es la norma, pero el algoritmo también funciona para algo como: 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(); } }
fuente
Podrías lanzar el
double
todecimal
y luego hacerloToString()
.(0.000000005).ToString() // 5E-09 ((decimal)(0.000000005)).ToString() // 0,000000005
No he realizado pruebas de rendimiento, que son más rápidas, pasando de 64 bits
double
a 128 bitsdecimal
o una cadena de formato de más de 300 caracteres. Ah, y posiblemente haya errores de desbordamiento durante la conversión, pero si sus valores se ajustan a,decimal
esto debería funcionar bien.Actualización: el casting parece ser mucho más rápido. Usando una cadena de formato preparada como se indica en la otra respuesta, formatear un millón de veces toma 2.3 segundos y transmitir solo 0.19 segundos. Repetible. Eso es 10 veces más rápido . Ahora solo se trata del rango de valores.
fuente
((decimal)(1e-200)).ToString()
por ejemplo, devuelve lo0
que está mal.double.ToString("0.############################")
. Según mi prueba, la tuya es solo 3 veces más rápida. De cualquier manera, es solo una respuesta válida si está seguro de que no necesita imprimir los dígitos a continuación1e-28
y que su doble no es grande, los cuales no son restricciones en la pregunta original.Esto es lo que tengo hasta ahora, parece funcionar, pero tal vez alguien tenga una mejor solución:
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));
fuente
double x = 1.0; for (int i = 0; i < 200; i++) x /= 10; Console.WriteLine(x);
El problema con
#.###...###
oF99
es que no conserva la precisión en los lugares decimales finales, por ejemplo:String t1 = (0.0001/7).ToString("0." + new string('#', 339)); // 0.0000142857142857143 String t2 = (0.0001/7).ToString("r"); // 1.4285714285714287E-05
El problema
DecimalConverter.cs
es que es lento. Este código es la misma idea que la respuesta de Sasik, pero el doble de rápido. Método de prueba unitaria en la parte inferior.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(); } } } }
fuente
La solución obligatoria basada en logaritmos. Tenga en cuenta que esta solución, debido a que implica hacer matemáticas, puede reducir un poco la precisión de su número. No muy probado.
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'); } }
Editar: Si el punto decimal cruza una parte distinta de cero del número, este algoritmo fallará estrepitosamente. Intenté lo simple y fui demasiado lejos.
fuente
En los viejos tiempos, cuando teníamos que escribir nuestros propios formateadores, aislábamos la mantisa y el exponente y los formateábamos por separado.
En este artículo de Jon Skeet ( https://csharpindepth.com/articles/FloatingPoint ), proporciona un enlace a su rutina DoubleConverter.cs que debería hacer exactamente lo que quieres. Skeet también se refiere a esto al extraer mantisa y exponente de doble en c # .
fuente
Acabo de improvisar en el código anterior para que funcione con valores exponenciales negativos.
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); } } } } }
Este código toma un directorio de entrada y basado en el delimitador convierte todos los valores en notación científica a formato numérico.
Gracias
fuente
prueba este:
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; }
fuente
Al ser millones de programadores en todo el mundo, siempre es una buena práctica intentar buscar si alguien ya se ha topado con su problema. A veces, las soluciones son basura, lo que significa que es hora de escribir las tuyas propias, y a veces las hay excelentes, como las siguientes:
http://www.yoda.arachsys.com/csharp/DoubleConverter.cs
(detalles: http://www.yoda.arachsys.com/csharp/floatingpoint.html )
fuente
string strdScaleFactor = dScaleFactor.ToString(); // where dScaleFactor = 3.531467E-05 decimal decimalScaleFactor = Decimal.Parse(strdScaleFactor, System.Globalization.NumberStyles.Float);
fuente
Podría estar equivocado, pero ¿no es así?
data.ToString("n");
http://msdn.microsoft.com/en-us/library/dwhawy9k.aspx
fuente
Solo para construir sobre lo que dijo jcasso, lo que puede hacer es ajustar su valor doble cambiando el exponente para que su formato favorito lo haga por usted, aplique el formato y luego rellene el resultado con ceros para compensar el ajuste.
fuente
Creo que solo necesitas usar IFormat con
ejemplo:
double d = double.MaxValue; string s = d.ToString(d, System.Globalization.NumberStyles.Number);
fuente
Mi solución fue usar los formatos personalizados. prueba esto:
double d; d = 1234.12341234; d.ToString("#########0.#########");
fuente
d = 1.5E200
yd = 1E-200
. La cadena resultante debe tener casi 2000
caracteres o su solución no funcionará.doubleValue.ToString("0." + new string('#', 339))
no tiene pérdidas. Compare estos métodos usando el valordouble.Epsilon
.Esto funciona bien para mi...
double number = 1.5E+200; string s = number.ToString("#"); //Output: "150000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
fuente
1.5e-200
.