¿Cómo analizo una cadena con un punto decimal en un doble?

231

Quiero analizar una cadena como "3.5"un doble. Sin embargo,

double.Parse("3.5") 

rinde 35 y

double.Parse("3.5", System.Globalization.NumberStyles.AllowDecimalPoint) 

lanza a FormatException.

Ahora la configuración regional de mi computadora está configurada en alemán, en donde se usa una coma como separador decimal. Puede que tenga que hacer algo con eso y double.Parse()esperar "3,5"como entrada, pero no estoy seguro.

¿Cómo puedo analizar una cadena que contiene un número decimal que puede o no formatearse como se especifica en mi ubicación actual?

CodeCaster
fuente
La coma decimal ciertamente afectará la salida.
ChrisF
12
No se olvide del método double. TryParse (), si es apropiado para su situación.
Kyle Gagnet

Respuestas:

414
double.Parse("3.5", CultureInfo.InvariantCulture)
Mehrdad Afshari
fuente
Me gusta usar la XmlConvertclase ... ¿Tienes alguna idea de si esto es mejor, peor y / o diferente que usar CultureInfo.InvariantCulture?
ChrisW
1
Bueno, en XmlConvertrealidad no está destinado a usarse para analizar un solo valor doble en el código. Prefiero usar double.Parseo Convert.ToDoubleeso hace que mi intención sea obvia.
Mehrdad Afshari
44
Esto significa doulble.Parse utiliza la cultura predeterminada que puede no contener punto como punto decimal.
Ahmed dijo el
3
si convierte 12345678.12345678, también convierte 12345678.123457
PUG
44
no funcionó para mí: omite la coma y regresa e int como doble
fnc12
75

Normalmente uso una función multicultural para analizar la entrada del usuario, principalmente porque si alguien está acostumbrado al teclado numérico y está usando una cultura que usa una coma como separador decimal, esa persona usará el punto del teclado numérico en lugar de una coma.

public static double GetDouble(string value, double defaultValue)
{
    double result;

    //Try parsing in the current culture
    if (!double.TryParse(value, System.Globalization.NumberStyles.Any, CultureInfo.CurrentCulture, out result) &&
        //Then try in US english
        !double.TryParse(value, System.Globalization.NumberStyles.Any, CultureInfo.GetCultureInfo("en-US"), out result) &&
        //Then in neutral language
        !double.TryParse(value, System.Globalization.NumberStyles.Any, CultureInfo.InvariantCulture, out result))
    {
        result = defaultValue;
    }

    return result;
}

Sin embargo, ten cuidado, los comentarios de @nikie son ciertos. En mi defensa, uso esta función en un entorno controlado donde sé que la cultura puede ser en-US, en-CA o fr-CA. Uso esta función porque en francés, usamos la coma como separador decimal, pero cualquiera que haya trabajado en finanzas siempre usará el separador decimal en el teclado numérico, pero este es un punto, no una coma. Entonces, incluso en la cultura fr-CA, necesito analizar un número que tendrá un punto como separador decimal.

Pierre-Alain Vigeant
fuente
18
No estoy seguro de que sea una buena idea. No puede analizar de forma confiable un doble si no conoce la cultura: en Alemania, los valores dobles pueden contener '.'s, pero se consideran allí como miles de separadores. Entonces, en el caso de Legate, GetDouble ("3.5") devolvería 35 en un entorno alemán y 3.5 en un entorno en-us.
Niki
No, Pierre Alain tiene razón, ya que está exactamente como está escrito. Si su cultura dice que el separador "el punto es mil", entonces "3.5" se ve como "35" y está bien. Sin embargo, si la cultura no tiene reglas para el "punto", entonces el carácter se analiza como un punto decimal, y también funciona. Hubiera evitado probar la cultura enUS, pero es una elección personal.
xryl669
Si usa el teclado numérico en cultura con coma, la tecla de puntos se establecerá con coma.
CrazyBaran
El separador decimal del teclado numérico depende de la distribución del teclado (y no de la configuración regional, al menos en Windows 7) (uso húngaro para escribir texto, correos electrónicos ... y en-EE. UU. Para escribir código, por lo que puede ser cualquier punto o coma. También uso configuraciones regionales personalizadas donde cambié el separador decimal de ',' a '.' y el separador de lista de ';' a ','. Ya sabes, causa csv ... Buena suerte para todos nosotros escribiendo multi -culture apps;)
Steven Spark
25

No pude escribir un comentario, así que escribo aquí:

double.Parse ("3.5", CultureInfo.InvariantCulture) no es una buena idea, porque en Canadá escribimos 3.5 en lugar de 3.5 y esta función nos da 35 como resultado.

Probé ambos en mi computadora:

double.Parse("3.5", CultureInfo.InvariantCulture) --> 3.5 OK
double.Parse("3,5", CultureInfo.InvariantCulture) --> 35 not OK

Esta es una forma correcta que mencionó Pierre-Alain Vigeant

public static double GetDouble(string value, double defaultValue)
{
    double result;

    // Try parsing in the current culture
    if (!double.TryParse(value, System.Globalization.NumberStyles.Any, CultureInfo.CurrentCulture, out result) &&
        // Then try in US english
        !double.TryParse(value, System.Globalization.NumberStyles.Any, CultureInfo.GetCultureInfo("en-US"), out result) &&
        // Then in neutral language
        !double.TryParse(value, System.Globalization.NumberStyles.Any, CultureInfo.InvariantCulture, out result))
    {
        result = defaultValue;
    }
    return result;
}
Malus Jan
fuente
1
Re: "... porque en Canadá escribimos 3,5 en lugar de 3,5" ¿Estás seguro de eso? Según la marca decimal : "Los países donde se utiliza un punto". "Como marca decimal incluyen ... Canadá (cuando se usa inglés)" . ¿No se trata más de usar una versión francesa de Windows?
Peter Mortensen
Baybe por la versión francesa. En Montreal escribimos 3,5, no 3,5
Malus Jan
Entonces, según su lógica, ¿solo 1 de ellos devuelve verdadero?
Batmaci
Mira que
Malus Ene
Todavía hay un error. Para la cadena de entrada como GetDouble ("10 ,,,,,,,, 0", 0.0). La función mencionada devuelve 100.
Krivers
21
Double.Parse("3,5".Replace(',', '.'), CultureInfo.InvariantCulture)

Reemplace la coma con un punto antes de analizar. Útil en países con una coma como separador decimal. Piense en limitar la entrada del usuario (si es necesario) a una coma o punto.

Baluda
fuente
1
Respuesta mucho más correcta que la que tiene +133 votos ... Permite vivir en ambos sistemas con "," o "". separador decimal ...
Badiboy
@Badiboy, ¿puedes explicar qué hay de malo en esta respuesta? Según tengo entendido, InvariantCulture siempre tiene '.' como separador decimal Por lo tanto, debería funcionar para ambos sistemas.
Alex P.
@ Alex11223 Tienes razón. Es por eso que dije que esta respuesta es mejor que una más popular. PD: Hablando amigablemente este código también fallará si tiene el "," como SEPARADOR DE LISTA (es decir, 1,234,560.01), pero no sé cómo resolver esto en absoluto. :)
Badiboy
44
Esta no es una buena respuesta porque en algunos cultureinfos, es el separador de miles y puede usarse. Si lo reemplaza por un punto, terminará teniendo varios puntos y el análisis fallará: Double.Parse ((12345.67) .ToString ("N", new CultureInfo ("en")). Reemplazar (',', '. '), CultureInfo.InvariantCulture) porque (12345.67) .ToString ("N", new CultureInfo ("en")). Reemplazar (', ','. ') Se formateará como "12.345.67"
codingdave
1,234.56 -> 1.234.56 no analizador. Otra idea es verificar si el número contiene '.' y ',' y reemplazar ',' con una cadena vacía y si solo ',' la coma presentada lo reemplaza por '.'
GDocal
16

El truco es utilizar una cultura invariante, analizar el punto en todas las culturas.

double.Parse("3.5", System.Globalization.NumberStyles.AllowDecimalPoint, System.Globalization.NumberFormatInfo.InvariantInfo);
Yakeen
fuente
11

Mire, cada respuesta anterior que propone escribir un reemplazo de cadena por una cadena constante solo puede ser incorrecta. ¿Por qué? ¡Porque no respetas la configuración regional de Windows! Windows garantiza al usuario la libertad de establecer el carácter separador que desee. Él / ella puede abrir el panel de control, ir al panel de región, hacer clic en avanzado y cambiar el personaje en cualquier momento. Incluso durante la ejecución de su programa. Piensa en esto. Una buena solución debe ser consciente de esto.

Entonces, primero tendrá que preguntarse, de dónde proviene este número, que desea analizar. Si proviene de la entrada en .NET Framework, no hay problema, porque estará en el mismo formato. Pero tal vez provenía de afuera, tal vez de un servidor externo, tal vez de un DB antiguo que solo admite propiedades de cadena. Allí, el administrador de db debería haber dado una regla en qué formato se deben almacenar los números. Si sabe, por ejemplo, que será una base de datos estadounidense con formato estadounidense, puede usar este código:

CultureInfo usCulture = new CultureInfo("en-US");
NumberFormatInfo dbNumberFormat = usCulture.NumberFormat;
decimal number = decimal.Parse(db.numberString, dbNumberFormat);

Esto funcionará bien en cualquier parte del mundo. Y no use 'Convert.ToXxxx'. La clase 'Convertir' se considera solo como una base para conversiones en cualquier dirección. Además: también puede utilizar el mecanismo similar para DateTimes.

AndresRohrAtlasInformatik
fuente
¡Convenido! Intentar implementar manualmente las características de Cultura eventualmente resultará en un caso que no esperaba y un gran dolor de cabeza. Deje que .NET lo maneje adecuadamente.
Khalos
2
un gran problema es cuando los usuarios están utilizando un separador decimal que no se considera el separador decimal para sus entornos culturales
EdmundYeung99
3
string testString1 = "2,457";
string testString2 = "2.457";    
double testNum = 0.5;
char decimalSepparator;
decimalSepparator = testNum.ToString()[1];

Console.WriteLine(double.Parse(testString1.Replace('.', decimalSepparator).Replace(',', decimalSepparator)));
Console.WriteLine(double.Parse(testString2.Replace('.', decimalSepparator).Replace(',', decimalSepparator)));
Martín
fuente
2

Mis dos centavos sobre este tema, tratando de proporcionar un método genérico de doble conversión:

private static double ParseDouble(object value)
{
    double result;

    string doubleAsString = value.ToString();
    IEnumerable<char> doubleAsCharList = doubleAsString.ToList();

    if (doubleAsCharList.Where(ch => ch == '.' || ch == ',').Count() <= 1)
    {
        double.TryParse(doubleAsString.Replace(',', '.'),
            System.Globalization.NumberStyles.Any,
            CultureInfo.InvariantCulture,
            out result);
    }
    else
    {
        if (doubleAsCharList.Where(ch => ch == '.').Count() <= 1
            && doubleAsCharList.Where(ch => ch == ',').Count() > 1)
        {
            double.TryParse(doubleAsString.Replace(",", string.Empty),
                System.Globalization.NumberStyles.Any,
                CultureInfo.InvariantCulture,
                out result);
        }
        else if (doubleAsCharList.Where(ch => ch == ',').Count() <= 1
            && doubleAsCharList.Where(ch => ch == '.').Count() > 1)
        {
            double.TryParse(doubleAsString.Replace(".", string.Empty).Replace(',', '.'),
                System.Globalization.NumberStyles.Any,
                CultureInfo.InvariantCulture,
                out result);
        }
        else
        {
            throw new ParsingException($"Error parsing {doubleAsString} as double, try removing thousand separators (if any)");
        }
    }

    return result;
}

Funciona como se espera con:

  • 1.1
  • 1,1
  • 1,000,000,000
  • 1.000.000.000
  • 1,000,000,000.99
  • 1.000.000.000,99
  • 5,000,111.3
  • 5.000.111,3
  • 0.99,000,111,88
  • 0,99.000.111.88

Ninguna conversión predeterminado se implementa, por lo que sería un error intentar analizar 1.3,14, 1,3.14o casos similares.

eduherminio
fuente
1
"1,000" destinado a mil fracasará.
Defkon1
1

El siguiente código hace el trabajo en cualquier escenario. Está un poco analizando.

List<string> inputs = new List<string>()
{
    "1.234.567,89",
    "1 234 567,89",
    "1 234 567.89",
    "1,234,567.89",
    "123456789",
    "1234567,89",
    "1234567.89",
};
string output;

foreach (string input in inputs)
{
    // Unify string (no spaces, only .)
    output = input.Trim().Replace(" ", "").Replace(",", ".");

    // Split it on points
    string[] split = output.Split('.');

    if (split.Count() > 1)
    {
        // Take all parts except last
        output = string.Join("", split.Take(split.Count()-1).ToArray());

        // Combine token parts with last part
        output = string.Format("{0}.{1}", output, split.Last());
    }

    // Parse double invariant
    double d = double.Parse(output, CultureInfo.InvariantCulture);
    Console.WriteLine(d);
}
JanW
fuente
2
1.234.567.890 regresaría 1234567.890
Dan Vogel el
No lo he intentado, pero si ejecuta la aplicación en SO de cultura diferente, este código no sería el truco, creo: /
Dani bISHOP
1

Creo que la conversión 100% correcta no es posible si el valor proviene de una entrada del usuario. por ejemplo, si el valor es 123.456, puede ser una agrupación o puede ser un punto decimal. Si realmente necesita el 100%, debe describir su formato y lanzar una excepción si no es correcto.

Pero mejoré el código de JanW, por lo que avanzamos un poco más al 100%. La idea detrás es que, si el último separador es un groupSeperator, sería más un tipo entero que un doble.

El código agregado está en el primero si es de GetDouble .

void Main()
{
    List<string> inputs = new List<string>() {
        "1.234.567,89",
        "1 234 567,89",
        "1 234 567.89",
        "1,234,567.89",
        "1234567,89",
        "1234567.89",
        "123456789",
        "123.456.789",
        "123,456,789,"
    };

    foreach(string input in inputs) {
        Console.WriteLine(GetDouble(input,0d));
    }

}

public static double GetDouble(string value, double defaultValue) {
    double result;
    string output;

    // Check if last seperator==groupSeperator
    string groupSep = System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator;
    if (value.LastIndexOf(groupSep) + 4 == value.Count())
    {
        bool tryParse = double.TryParse(value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.CurrentCulture, out result);
        result = tryParse ? result : defaultValue;
    }
    else
    {
        // Unify string (no spaces, only . )
        output = value.Trim().Replace(" ", string.Empty).Replace(",", ".");

        // Split it on points
        string[] split = output.Split('.');

        if (split.Count() > 1)
        {
            // Take all parts except last
            output = string.Join(string.Empty, split.Take(split.Count()-1).ToArray());

            // Combine token parts with last part
            output = string.Format("{0}.{1}", output, split.Last());
        }
        // Parse double invariant
        result = double.Parse(output, System.Globalization.CultureInfo.InvariantCulture);
    }
    return result;
}
Schorsch
fuente
1
        var doublePattern = @"(?<integer>[0-9]+)(?:\,|\.)(?<fraction>[0-9]+)";
        var sourceDoubleString = "03444,44426";
        var match = Regex.Match(sourceDoubleString, doublePattern);

        var doubleResult = match.Success ? double.Parse(match.Groups["integer"].Value) + (match.Groups["fraction"].Value == null ? 0 : double.Parse(match.Groups["fraction"].Value) / Math.Pow(10, match.Groups["fraction"].Value.Length)): 0;
        Console.WriteLine("Double of string '{0}' is {1}", sourceDoubleString, doubleResult);
Alejandro
fuente
0

En lugar de tener que especificar una configuración regional en todos los análisis, prefiero establecer una configuración regional amplia de la aplicación, aunque si los formatos de cadena no son consistentes en la aplicación, esto podría no funcionar.

CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("pt-PT");
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("pt-PT");

Definir esto al comienzo de su aplicación hará que todos los análisis dobles esperen una coma como delimitador decimal. Puede establecer una configuración regional adecuada para que el separador decimal y de miles se ajuste a las cadenas que está analizando.

Miguel Mesquita Alfaiate
fuente
0

Es difícil sin especificar qué separador decimal buscar, pero si lo hace, esto es lo que estoy usando:

    public static double Parse(string str, char decimalSep)
    {
        string s = GetInvariantParseString(str, decimalSep);
        return double.Parse(s, System.Globalization.CultureInfo.InvariantCulture);
    }

    public static bool TryParse(string str, char decimalSep, out double result)
    {
        // NumberStyles.Float | NumberStyles.AllowThousands got from Reflector
        return double.TryParse(GetInvariantParseString(str, decimalSep), NumberStyles.Float | NumberStyles.AllowThousands, System.Globalization.CultureInfo.InvariantCulture, out result);
    }

    private static string GetInvariantParseString(string str, char decimalSep)
    {
        str = str.Replace(" ", "");

        if (decimalSep != '.')
            str = SwapChar(str, decimalSep, '.');

        return str;
    }
    public static string SwapChar(string value, char from, char to)
    {
        if (value == null)
            throw new ArgumentNullException("value");

        StringBuilder builder = new StringBuilder();

        foreach (var item in value)
        {
            char c = item;
            if (c == from)
                c = to;
            else if (c == to)
                c = from;

            builder.Append(c);
        }
        return builder.ToString();
    }

    private static void ParseTestErr(string p, char p_2)
    {
        double res;
        bool b = TryParse(p, p_2, out res);
        if (b)
            throw new Exception();
    }

    private static void ParseTest(double p, string p_2, char p_3)
    {
        double d = Parse(p_2, p_3);
        if (d != p)
            throw new Exception();
    }

    static void Main(string[] args)
    {
        ParseTest(100100100.100, "100.100.100,100", ',');
        ParseTest(100100100.100, "100,100,100.100", '.');
        ParseTest(100100100100, "100.100.100.100", ',');
        ParseTest(100100100100, "100,100,100,100", '.');
        ParseTestErr("100,100,100,100", ',');
        ParseTestErr("100.100.100.100", '.');
        ParseTest(100100100100, "100 100 100 100.0", '.');
        ParseTest(100100100.100, "100 100 100.100", '.');
        ParseTest(100100100.100, "100 100 100,100", ',');
        ParseTest(100100100100, "100 100 100,100", '.');
        ParseTest(1234567.89, "1.234.567,89", ',');    
        ParseTest(1234567.89, "1 234 567,89", ',');    
        ParseTest(1234567.89, "1 234 567.89",     '.');
        ParseTest(1234567.89, "1,234,567.89",    '.');
        ParseTest(1234567.89, "1234567,89",     ',');
        ParseTest(1234567.89, "1234567.89",  '.');
        ParseTest(123456789, "123456789", '.');
        ParseTest(123456789, "123456789", ',');
        ParseTest(123456789, "123.456.789", ',');
        ParseTest(1234567890, "1.234.567.890", ',');
    }

Esto debería funcionar con cualquier cultura. No puede analizar correctamente las cadenas que tienen más de un separador decimal, a diferencia de las implementaciones que reemplazan en lugar de intercambiar.

osexpert
fuente
0

También mejoré el código de @JanW ...

Lo necesito para formatear resultados de instrumentos médicos, y también envían "> 1000", "23.3e02", "350E-02" y "NEGATIVO".

private string FormatResult(string vResult)
{
  string output;
  string input = vResult;

  // Unify string (no spaces, only .)
  output = input.Trim().Replace(" ", "").Replace(",", ".");

  // Split it on points
  string[] split = output.Split('.');

  if (split.Count() > 1)
  {
    // Take all parts except last
    output = string.Join("", split.Take(split.Count() - 1).ToArray());

    // Combine token parts with last part
    output = string.Format("{0}.{1}", output, split.Last());
  }
  string sfirst = output.Substring(0, 1);

  try
  {
    if (sfirst == "<" || sfirst == ">")
    {
      output = output.Replace(sfirst, "");
      double res = Double.Parse(output);
      return String.Format("{1}{0:0.####}", res, sfirst);
    }
    else
    {
      double res = Double.Parse(output);
      return String.Format("{0:0.####}", res);
    }
  }
  catch
  {
    return output;
  }
}
JacekK
fuente
-2
System.Globalization.CultureInfo ci = System.Globalization.CultureInfo.CurrentCulture;

string _pos = dblstr.Replace(".",
    ci.NumberFormat.NumberDecimalSeparator).Replace(",",
        ci.NumberFormat.NumberDecimalSeparator);

double _dbl = double.Parse(_pos);
Kartal Turgut
fuente
-3

Creo que es la mejor respuesta:

public static double StringToDouble(string toDouble)
{
    toDouble = toDouble.Replace(",", "."); //Replace every comma with dot

    //Count dots in toDouble, and if there is more than one dot, throw an exception.
    //Value such as "123.123.123" can't be converted to double
    int dotCount = 0;
    foreach (char c in toDouble) if (c == '.') dotCount++; //Increments dotCount for each dot in toDouble
    if (dotCount > 1) throw new Exception(); //If in toDouble is more than one dot, it means that toCount is not a double

    string left = toDouble.Split('.')[0]; //Everything before the dot
    string right = toDouble.Split('.')[1]; //Everything after the dot

    int iLeft = int.Parse(left); //Convert strings to ints
    int iRight = int.Parse(right);

    //We must use Math.Pow() instead of ^
    double d = iLeft + (iRight * Math.Pow(10, -(right.Length)));
    return d;
}
Endorfina
fuente
Explicar su código y proporcionar más detalles sería útil.
Charlie Fish
¿Qué explicar aquí? Todo está en comentarios
Endorphinex
-3

Lo siguiente es menos eficiente, pero uso esta lógica. Esto es válido solo si tiene dos dígitos después del punto decimal.

double val;

if (temp.Text.Split('.').Length > 1)
{
    val = double.Parse(temp.Text.Split('.')[0]);

    if (temp.Text.Split('.')[1].Length == 1)
        val += (0.1 * double.Parse(temp.Text.Split('.')[1]));
    else
        val += (0.01 * double.Parse(temp.Text.Split('.')[1]));
}
else
    val = double.Parse(RR(temp.Text));
trabajo experto
fuente
-5

Multiplica el número y luego divídelo por lo que multiplicaste antes.

Por ejemplo,

perc = double.Parse("3.555)*1000;
result = perc/1000
percy
fuente