Conversión de doble a cadena sin notación científica
¿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.
Este no es un duplicado de Cómo convertir doble en cadena sin la representación de potencia a 10 (E-05) porque las respuestas dadas allí no resuelven el problema en cuestión. La solución aceptada en esta pregunta fue utilizar 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 debe abordar correctamente los formatos de números personalizados (por ejemplo, otro separador decimal, según la información cultural).
Editar: La pregunta en realidad es solo sobre mostrar los números antes mencionados. Sé cómo funcionan los números de coma flotante y qué números se pueden usar y calcular con ellos.
Para una solución de uso general¹ es necesario conservar 339 lugares:
doubleValue.ToString("0." + new string('#', 339))
El número máximo 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 hacia la derecha. ( Ver alcance y precisión. )
Funciona para double.Epsilon
, double.MinValue
, double.MaxValue
y cualquier cosa intermedia.
El rendimiento será mucho mayor que el de las soluciones de manipulación de cadenas/expresiones regulares, ya que todo el trabajo de formato y cadena se realiza en una sola pasada mediante código CLR no administrado. Además, es mucho más sencillo comprobar que el código es correcto.
Para facilitar el uso y lograr 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 que ToString
su visualización normal se redondea para todos los formatos excepto r
. Ejemplo vivo. ¡Gracias, @Loathing! Consulte la respuesta de Lothing si necesita la capacidad de realizar ida y vuelta en notación de punto fijo (es decir, si la está utilizando .ToString("r")
hoy).
Tuve un problema similar y esto funcionó para mí:
doubleValue.ToString("F99").TrimEnd('0')
F99 puede ser excesivo, pero se entiende la idea.
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 según lo solicitado.
Actualización : las pruebas de 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();
}
}
Podrías lanzar el double
to decimal
y luego hacerlo ToString()
.
(0.000000005).ToString() // 5E-09
((decimal)(0.000000005)).ToString() // 0,000000005
No he realizado pruebas de rendimiento que sean más rápidas, transmitiendo de 64 bits double
a 128 bits decimal
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 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 sólo se trata del rango de valores.
Esto es lo que tengo hasta ahora, parece funcionar, pero tal vez alguien tenga una solución mejor:
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));