c# - salida - Conversión de doble a cadena sin notación científica
string format c# separador de miles (17)
¿Cómo convertir un doble en una representación de cadena de coma 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úmero 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 a cadena sin la representación del poder al 10 (E-05) porque las respuestas dadas 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 formateo de punto fijo y recorte del 0 redundante tampoco resuelve el problema, ya que el ancho máximo para el ancho fijo es de 99 caracteres.
Nota: la solución tiene que tratar correctamente con formatos de números personalizados (por ejemplo, otro separador decimal, dependiendo de la información cultural).
Editar: la pregunta es realmente solo sobre la eliminación de los números mencionados anteriormente. Soy consciente de cómo funcionan los números flotantes y qué números pueden usarse y computarse con ellos.
Acabo de improvisar 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
Creo que solo necesita usar IFormat con
ToString(doubleVar, System.Globalization.NumberStyles.Number)
ejemplo:
double d = double.MaxValue;
string s = d.ToString(d, System.Globalization.NumberStyles.Number);
El problema con #.###...###
o F99
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 con DecimalConverter.cs
es que es lento. Este código es la misma idea que la respuesta de Sasik, pero dos veces más 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();
}
}
}
}
En los viejos tiempos cuando teníamos que escribir nuestros propios formateadores, aislamos la mantisa y el exponente y los formateamos por separado.
En este artículo de Jon Skeet ( http://www.yoda.arachsys.com/csharp/floatingpoint.html ) proporciona un enlace a su rutina DoubleConverter.cs que debería hacer exactamente lo que usted desea. Skeet también se refiere a esto al extraer mantisa y exponente del doble en c # .
Esta es una solución de análisis de cadenas donde el número de origen (doble) se convierte en una cadena y se analiza en sus componentes constituyentes. Luego es reensamblado por reglas en la representación numérica de longitud completa. También representa la configuración regional según lo solicitado.
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();
}
}
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));
Esto funciona bien para mi...
double number = 1.5E+200;
string s = number.ToString("#");
//Output: "150000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
La solución obligatoria basada en logaritmos. Tenga en cuenta que esta solución, porque 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 no nula del número, este algoritmo fallará miserablemente. Intenté por simple y fui demasiado lejos.
Mi solución fue usar los formatos personalizados. prueba esto:
double d;
d = 1234.12341234;
d.ToString("#########0.#########");
Para construir sobre lo que dijo jcasso, lo que puede hacer es ajustar su doble valor 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.
Para una solución de propósito general, debe conservar 339 lugares:
doubleValue.ToString("0." + new string(''#'', 339))
La cantidad máxima de dígitos decimales distintos de cero es 16. 15 están en el lado derecho del punto decimal. El exponente puede mover esos 15 dígitos un máximo de 324 lugares a la derecha. ( Ver el rango y la precisión )
Funciona para double.Epsilon
, double.MinValue
, double.MaxValue
y cualquier elemento intermedio.
El rendimiento será mucho mayor que las soluciones de manipulación de expresiones regulares / cadenas, ya que todo el formato y el trabajo de cadenas se realizan de una sola vez mediante código CLR no administrado. Además, el código es mucho más simple para demostrar que es correcto.
Para facilidad de uso y aún mejor rendimiento, hazlo 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 que ToString
hace su redondeo de pantalla normal para todos los formatos excepto r
. Ejemplo en vivo Gracias, @Loathing! Por favor, vea la respuesta de Lothing si necesita la capacidad de hacer un ida y vuelta en notación de punto fijo (es decir, si está usando .ToString("r")
hoy).
Podría estar equivocado, pero ¿no es así?
data.ToString("n");
Puede convertir el double
a decimal
y luego hacer ToString()
.
(0.000000005).ToString() // 5E-09
((decimal)(0.000000005)).ToString() // 0,000000005
No he hecho pruebas de rendimiento que sean más rápidas, desde el double
de 64 bits hasta el decimal
128 bits o una cadena de formato de más de 300 caracteres. Ah, y es posible que haya errores de desbordamiento durante la conversión, pero si sus valores se ajustan a un 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 lleva 2.3 segundos y solo lanza 0,19 segundos. Repetible Eso es 10 veces más rápido . Ahora solo se trata del rango de valores.
Siendo millones de programadores en todo el mundo, siempre es una buena práctica intentar buscar si alguien ya se ha topado con tu problema. A veces, las soluciones son basura, lo que significa que es hora de escribir la suya y, a veces, son excelentes, como las siguientes:
http://www.yoda.arachsys.com/csharp/DoubleConverter.cs
(detalles: http://www.yoda.arachsys.com/csharp/floatingpoint.html )
Tuve un problema similar y esto funcionó para mí:
doubleValue.ToString("F99").TrimEnd(''0'')
F99 puede ser exagerado, pero entiendes la idea.
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;
}
string strdScaleFactor = dScaleFactor.ToString(); // where dScaleFactor = 3.531467E-05
decimal decimalScaleFactor = Decimal.Parse(strdScaleFactor, System.Globalization.NumberStyles.Float);