Conversão de duplo para string sem notação científica

Como converter um duplo em uma representação de seqüência de ponto flutuante sem notação científica no .NET Framework?

Amostras “pequenas” (números efetivos podem ser de qualquer tamanho, como 1.5E200 ou 1e-200 ):

 3248971234698200000000000000000000000000000000 0.00000000000000000000000000000000000023897356978234562 

Nenhum dos formatos numéricos padrão é assim, e um formato personalizado também não parece permitir ter um número aberto de dígitos após o separador decimal.

Esta não é uma duplicata de Como converter o dobro em string sem o poder de 10 de representação (E-05) porque as respostas dadas lá não resolvem o problema em questão. A solução aceita nesta questão foi usar um ponto fixo (como 20 dígitos), que não é o que eu quero. Um ponto fixo formatando e aparando o 0 redundante não resolve o problema porque a largura máxima para largura fixa é de 99 caracteres.

Nota: a solução tem que lidar corretamente com formatos numéricos personalizados (por exemplo, outro separador decimal, dependendo da informação cultural).

Edit: A questão é realmente apenas sobre o deslocamento de números acima mencionados. Estou ciente de como funcionam os números de ponto flutuante e quais números podem ser usados ​​e computados com eles.

Para uma solução de propósito geral, você precisa preservar 339 lugares:

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

O número máximo de dígitos decimais diferentes de zero é 16. 15 estão no lado direito do ponto decimal. O expoente pode mover esses 15 dígitos, no máximo, para 324 lugares à direita. ( Veja o alcance e precisão. )

Ele funciona para double.Epsilon , double.MinValue , double.MaxValue e qualquer coisa entre.

O desempenho será muito maior do que as soluções de manipulação de regex / string, pois toda a formatação e o trabalho de string são feitos em uma passagem pelo código CLR não gerenciado. Além disso, o código é muito mais simples de se provar correto.

Para facilitar o uso e melhorar o desempenho, torne-o uma constante:

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

¹ Atualização: eu disse erroneamente que esta também era uma solução sem perdas. Na verdade, não é, já que o ToString faz seu arredondamento de exibição normal para todos os formatos, exceto r . Exemplo ao vivo. Obrigado, @Loathing! Por favor, veja a resposta de Lothing se você precisar da habilidade de fazer uma viagem de ida e volta na notação de ponto fixo (ou seja, se você estiver usando .ToString("r") hoje).

Eu tive um problema semelhante e isso funcionou para mim:

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

F99 pode ser um exagero, mas você entendeu.

Esta é uma solução de análise de string em que o número da fonte (double) é convertido em uma string e analisado em seus componentes constituintes. Em seguida, é reagrupada por regras na representação numérica completa. Também é responsável pela localidade conforme solicitado.

Atualização : os testes das conversões incluem apenas números inteiros de dígito único, que é a norma, mas o algoritmo também 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(); } } 

Você poderia converter o double em decimal e então fazer ToString() .

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

Eu não fiz testes de desempenho que são mais rápidos, convertendo de 64-bit double para 128-bit decimal ou uma string de formato de mais de 300 caracteres. Ah, e pode haver erros de estouro durante a conversão, mas se seus valores se ajustarem a um decimal isso deve funcionar bem.

Atualização: O casting parece ser muito mais rápido. Usando uma string de formatação preparada como dada na outra resposta, a formatação de um milhão de vezes leva 2,3 segundos e a transmissão de apenas 0,19 segundos. Repetivel. Isso é 10x mais rápido . Agora é apenas sobre o intervalo de valores.

Isso é o que eu tenho até agora, parece funcionar, mas talvez alguém tenha uma solução melhor:

 private static readonly Regex rxScientific = new Regex(@"^(?-?)(?\d+)(\.(?\d*?)0*)?E(?[+\-]\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)); 

Nos velhos tempos, quando tínhamos que escrever nossos próprios formatadores, isolávamos a mantissa e o expoente e os formatávamos separadamente.

Neste artigo de Jon Skeet ( http://www.yoda.arachsys.com/csharp/floatingpoint.html ) ele fornece um link para sua rotina DoubleConverter.cs que deve fazer exatamente o que você deseja. Skeet também se refere a isso ao extrair mantissa e expoente do dobro em c # .

O problema usando #.###...### ou F99 é que ele não preserva a precisão nas casas decimais do final, por exemplo:

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

O problema com DecimalConverter.cs é que é lento. Este código é a mesma idéia da resposta de Sasik, mas duas vezes mais rápido. Método de teste unitário na 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(); } } } } 

A solução obrigatória baseada em logaritmo. Note que esta solução, porque envolve matemática, pode reduzir um pouco a precisão do seu número. Não muito testado.

 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: Se o ponto decimal cruzar a parte diferente de zero do número, esse algoritmo falhará miseravelmente. Eu tentei por simples e fui longe demais.

Acabei de improvisar o código acima para fazer com que ele funcione para valores exponenciais 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); } } } } } 

Esse código usa um diretório de input e, com base no delimitador, converte todos os valores em notação científica para formato numérico.

obrigado

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

Sendo milhões de programadores em todo o mundo, é sempre uma boa prática tentar pesquisar se alguém já tiver encontrado seu problema. Às vezes, as soluções são lixo, o que significa que é hora de escrever as suas próprias e, às vezes, são ótimas, como as seguintes:

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

(detalhes: http://www.yoda.arachsys.com/csharp/floatingpoint.html )

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

Eu posso estar errado, mas não é assim?

 data.ToString("n"); 

http://msdn.microsoft.com/pt-br/library/dwhawy9k.aspx

Apenas para desenvolver o que você disse, o que você pode fazer é ajustar seu valor duplo alterando o expoente para que seu formato favorito faça isso para você, aplique o formato e aumente o resultado com zeros para compensar o ajuste.

Eu acho que você só precisa usar IFormat com

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

exemplo:

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

Minha solução estava usando os formatos personalizados. tente isto:

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

Este trabalho é bom para mim…

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