Conversión de doble a cadena sin notación científica

Resuelto Lucero asked hace 15 años • 18 respuestas

¿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.5E200o 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.

Lucero avatar Oct 10 '09 04:10 Lucero
Aceptado

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.MaxValuey 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 ToStringsu 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).

jnm2 avatar Nov 13 '2015 16:11 jnm2

Tuve un problema similar y esto funcionó para mí:

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

F99 puede ser excesivo, pero se entiende la idea.

Robert Lamm avatar Aug 11 '2011 20:08 Robert Lamm

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();
    }
}
Paul Sasik avatar Oct 15 '2009 02:10 Paul Sasik

Podrías lanzar el doubleto decimaly 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 doublea 128 bits decimalo 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 decimalesto 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.

ygoe avatar Mar 24 '2016 15:03 ygoe

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));
Lucero avatar Oct 09 '2009 21:10 Lucero