Duplo para conversão de seqüência de caracteres sem notação científica
-
20-09-2019 - |
Pergunta
Como converter um casal em um ponto flutuante de representação de seqüência de caracteres sem notação científica no .NET Framework?
"Pequeno" amostras (efetivo números podem ser de qualquer tamanho, tais como 1.5E200
ou 1e-200
) :
3248971234698200000000000000000000000000000000
0.00000000000000000000000000000000000023897356978234562
Nenhum dos padrão de formatos de número são como este, e um formato personalizado também não parece permitir ter aberto um número de dígitos após o separador decimal.
Esta não é uma duplicata de Como converter duplo para a cadeia, sem o poder de 10 de representação (E-05) porque as respostas dadas não não resolver o problema em questão.A solução aceita nesta questão foi a utilização de um ponto fixo (por exemplo, 20 dígitos), o que não é o que eu quero.De um ponto fixo, formatação e aparar redundante 0 não resolve o problema porque a largura máxima de largura fixa é de 99 caracteres.
Nota: a solução tem de lidar corretamente com formatos de número personalizados (por exemplo,outros separador decimal, dependendo de informações de cultura).
Editar: A questão é realmente apenas sobre displaing números acima mencionados.Estou ciente de como números de ponto flutuante de trabalho e que podem ser utilizados números e calculado com eles.
Solução
Geral-purpose1 solução que você precisa para preservar 339 lugares:
doubleValue.ToString("0." + new string('#', 339))
O número máximo de zero casas decimais é de 16 anos.15 estão no lado direito do ponto decimal.O expoente pode mover os 15 dígitos no máximo 324 lugares para a direita.(Ver o alcance e a precisão.)
Ele funciona para double.Epsilon
, double.MinValue
, double.MaxValue
, e qualquer coisa in-between.
O desempenho será muito maior do que a regex/manipulação de seqüência de caracteres de soluções, pois toda a formatação e a seqüência de trabalho é feito em uma única passagem pela CLR não gerenciado código.Além disso, o código é muito simples para provar a correta.
Para facilidade de uso e desempenho ainda melhor, torná-la uma constante:
public static class FormatStrings
{
public const string DoubleFixedPoint = "0.###################################################################################################################################################################################################################################################################################################################################################";
}
¹ Atualização: Eu, por engano, disse que este foi também um sem perdas solução.Na verdade não é, uma vez que ToString
faz a sua exibição normal arredondamento para todos os formatos, exceto r
. Exemplo vivo. Obrigado, @Ódio!Por favor, consulte Lothing resposta se você precisa ter a capacidade de ida e volta em ponto fixo notação (que eu.e, se você estiver usando .ToString("r")
hoje).
Outras dicas
Eu tive um problema semelhante e isso funcionou para mim:
doubleValue.ToString("F99").TrimEnd('0')
F99 pode ser um exagero, mas você começa a idéia.
Esta é uma análise de seqüência de caracteres de solução, onde o número de origem (duplo) é convertido em uma seqüência de caracteres e analisado em seus componentes constituintes.Em seguida, ele é remontado por um conjunto de regras para o full-length representação numérica.Ele também contas para o local solicitado.
Atualização:Os testes de conversões de incluir somente de um único dígito números inteiros, que é a norma, mas o algoritmo também trabalha para algo como:239483.340901 e-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 conjurar o double
para decimal
e, em seguida, fazer ToString()
.
(0.000000005).ToString() // 5E-09
((decimal)(0.000000005)).ToString() // 0,000000005
Eu ainda não fiz o teste de desempenho que é mais rápido, lançando a partir de 64 bits double
128-bits decimal
ou uma cadeia de caracteres de formato, com mais de 300 caracteres.Ah, e não poderia possivelmente ser estouro erros durante a conversão, mas se os valores de ajuste de um decimal
isso deve funcionar bem.
Atualização: O elenco parece ser muito mais rápido.Usando um preparado cadeia de caracteres de formato, como dado na outra resposta, a formatação de um milhão de vezes leva 2,3 segundos e fundição de apenas 0,19 segundos.Repetitivo.O que é 10x mais rápido.Agora é só sobre o intervalo de valores.
É isso que eu fiquei tão longe, parece funcionar, mas talvez alguém tenha uma solução melhor:
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));
O problema usando #.###...###
ou F99
é que ele não preserva a precisão no final casas decimais, e.g:
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 ele é lento.Este código é o mesmo ideia de como Sasik a resposta, mas duas vezes mais rápido.Unidade método de ensaio 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();
}
}
}
}
Nos velhos tempos, quando tínhamos de escrever nossos próprios formatadores, gostaríamos de isolar a mantissa e o expoente e formatá-los separadamente.
Neste artigo, Jon Skeet (https://csharpindepth.com/articles/FloatingPoint ele fornece um link para o seu DoubleConverter.cs rotina que deve fazer exatamente o que você deseja.Skeet também se refere a isso em a extração de mantissa e expoente da dupla em c#.
Obrigatoriedade do Logaritmo de base de solução.Note que esta solução, pois envolve a fazer matemática, podem reduzir a precisão do seu número um pouco.Não muito testados.
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: Se o ponto decimal cruzamentos de zero parte do número, este algoritmo irá falhar miseravelmente.Eu tentei por simples e foi longe demais.
Eu tenho apenas improvisado no código acima para fazer o trabalho para negativo exponencial de valores.
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 entra em um diretório de entrada e com base no delimitador converte todos os valores em notação científica para o 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 para tentar procurar se alguém esbarrou em seu problema já.Às vezes, há soluções de lixo, o que significa que é hora de escrever o seu próprio, e, às vezes, não são grandes, tais como os 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 poderia estar errado, mas não é assim?
data.ToString("n");
Apenas para criar o que jcasso disse o que você pode fazer é ajustar o seu valor duplo alterando o expoente de modo que seu formato favorito faria isso por você, aplicar o formato, e que a almofada 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);
A minha solução foi utilizar os formatos personalizados.tente isso:
double d;
d = 1234.12341234;
d.ToString("#########0.#########");
Isso funciona bem para mim...
double number = 1.5E+200;
string s = number.ToString("#");
//Output: "150000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"