Como analiso uma string com um ponto decimal para um dobro?

231

Eu quero analisar uma string como "3.5"uma dupla. Contudo,

double.Parse("3.5") 

produz 35 e

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

joga um FormatException.

Agora, a localidade do meu computador está definida como alemão, em que uma vírgula é usada como separador decimal. Pode ter algo a ver com isso e double.Parse()esperar "3,5"como entrada, mas não tenho certeza.

Como posso analisar uma sequência contendo um número decimal que pode ou não ser formatado conforme especificado no meu código de idioma atual?

CodeCaster
fonte
A vírgula decimal certamente afetará a saída.
ChrisF
12
Não se esqueça do método double.TryParse (), se for apropriado para sua situação.
Kyle Gagnet

Respostas:

414
double.Parse("3.5", CultureInfo.InvariantCulture)
Mehrdad Afshari
fonte
Eu gosto de usar a XmlConvertclasse ... você tem alguma idéia de que isso é melhor, pior e / ou diferente do que usar CultureInfo.InvariantCulture?
ChrisW
1
Bem, XmlConvertnão se destina realmente a ser usado para analisar um único valor duplo no código. Eu prefiro usar double.Parseou Convert.ToDoubleque tornem minha intenção óbvia.
Mehrdad Afshari 30/08/09
4
Isso significa doulble.Parse usa a cultura padrão que pode não conter ponto como ponto decimal ??
Ahmed Said
3
se estiver convertendo 12345678.12345678, ele também converte 12345678.123457
PUG
4
não funcionou para mim: pula vírgula e retornos e int como duplo
fnc12
75

Normalmente, uso uma função multicultural para analisar a entrada do usuário, principalmente porque se alguém está acostumado ao numpad e está usando uma cultura que usa vírgula como separador decimal, essa pessoa usará o ponto do numpad em vez de vírgula.

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

Cuidado, porém, os comentários do @nikie são verdadeiros. Para minha defesa, uso essa função em um ambiente controlado, onde sei que a cultura pode ser en-US, en-CA ou fr-CA. Uso essa função porque, em francês, usamos a vírgula como um separador decimal, mas quem já trabalhou em finanças sempre usará o separador decimal no teclado numérico, mas esse é um ponto, não uma vírgula. Portanto, mesmo na cultura fr-CA, preciso analisar o número que terá um ponto como separador decimal.

Vigilante Pierre-Alain
fonte
18
Não sei se é uma boa ideia. Você não pode analisar com segurança um duplo se não conhece a cultura: na Alemanha, os valores duplos podem conter '.', Mas são considerados lá milhares separadores. Portanto, no caso de Legate, GetDouble ("3.5") retornaria 35 em uma localidade alemã e 3.5 em um ambiente en-us.
Niki
Não, Pierre Alain está certo, como está exatamente como está escrito. Se sua cultura disser "separador é um mil", então "3,5" será visto como "35" e isso é bom. No entanto, se você cultivar como nenhuma regra para o "ponto", o caractere será analisado como um ponto decimal e também funcionará. Eu teria evitado tentar a cultura enUS, mas é uma escolha pessoal.
precisa saber é o seguinte
Se você usar o numpad em cultura com vírgula, a tecla de ponto será definida como vírgula.
CrazyBaran
O separador decimal do numpad depende do layout do teclado (e não das configurações regionais - pelo menos no Windows 7) (eu uso húngaro para escrever texto, e-mails ... e en-US para escrever código, para que possa ser um ponto ou vírgula.Eu também uso configurações regionais personalizadas, onde alterei o separador decimal de ',' para '.' e o separador de lista de ';' para ','. Você sabe, causa csv ... Boa sorte para todos nós que escrevemos vários aplicativos de cultura;)
Steven Spark
25

Não consegui escrever um comentário, então escrevo aqui:

double.Parse ("3.5", CultureInfo.InvariantCulture) não é uma boa ideia, porque no Canadá escrevemos 3,5 em vez de 3,5 e essa função nos fornece 35 como resultado.

Eu testei os dois no meu computador:

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

Esta é a maneira correta que Pierre-Alain Vigeant mencionou

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
fonte
1
Re: "... porque no Canadá escrevemos 3,5 em vez de 3,5" Você tem certeza disso? De acordo com a marca decimal : "Os países onde um ponto". "É usado como marca decimal incluem ... Canadá (ao usar o inglês)" . Não se trata mais de usar uma versão francesa do Windows?
22816 Peter Mortensen
Baybe por causa da versão francesa. Em montreal, escrevemos 3,5, não 3,5
Malus Jan
Então, de acordo com sua lógica, sempre apenas 1 deles retorna verdadeiro?
batmaci
Veja
Malus Jan
Ainda existe um erro. Para a sequência de entrada como GetDouble ("10 ,,,,,,,, 0", 0.0). A função mencionada retorna 100.
Krivers
21
Double.Parse("3,5".Replace(',', '.'), CultureInfo.InvariantCulture)

Substitua a vírgula por um ponto antes de analisar. Útil em países com vírgula como separador decimal. Pense em limitar a entrada do usuário (se necessário) a uma vírgula ou ponto.

Baluda
fonte
1
Resposta muito mais correta do que a que tem +133 votos ... Permite viver nos dois sistemas com "," ou "." separador decimal ...
Badiboy 11/06
@Badiboy, você pode explicar o que há de errado com esta resposta? Pelo que entendi, a InvariantCulture sempre tem '.' como um separador decimal. Portanto, deve funcionar para os dois sistemas.
Alex P.
@ Alex11223 Você está certo. Por isso eu disse que essa resposta é melhor que a mais popular. PS: Em linguagem amigável, esse código também falhará se você tiver o "," como LIST SEPARATOR (ou seja, 1.234.560,01), mas não sei como resolver isso. :)
Badiboy 27/06
4
Esta não é uma boa resposta, porque em algumas culturas, o é o separador de milhares e pode ser usado. Se você substituí-lo por um ponto, acabará tendo vários pontos e a análise falhará: Double.Parse ((12345.67) .ToString ("N", new CultureInfo ("en")). Substitua (',', '. '), CultureInfo.InvariantCulture) porque (12345.67) .ToString ("N", new CultureInfo ("en")). Replace (', ','. ') Será formatado como "12.345.67"
codingdave
1.234,56 -> 1.234.56 não analisador. Outra idéia é verificar se o número contém '.' e ',' and replace ',' por string vazia e, se for apenas ',' vírgula apresentada substitua-a por '.'
GDocal 07/08/19
16

O truque é usar a cultura invariável, para analisar pontos em todas as culturas.

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

Olha, toda resposta acima que propõe escrever uma substituição de string por uma string constante só pode estar errada. Por quê? Porque você não respeita as configurações de região do Windows! O Windows garante ao usuário a liberdade de definir o caractere separador que ele deseja. Ele pode abrir o painel de controle, entrar no painel de região, clicar em avançado e alterar o caracter a qualquer momento. Mesmo durante a execução do seu programa. Pense nisso. Uma boa solução deve estar ciente disso.

Então, primeiro você terá que se perguntar, de onde vem esse número, que deseja analisar. Se for proveniente de entrada no .NET Framework, não há problema, pois estará no mesmo formato. Mas talvez tenha vindo de fora, talvez de um servidor externo, talvez de um banco de dados antigo que ofereça suporte apenas às propriedades da string. Lá, o administrador do banco de dados deve ter fornecido uma regra em que formato os números devem ser armazenados. Se você souber, por exemplo, que será um banco de dados dos EUA com o formato dos EUA, use este trecho de código:

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

Isso funcionará bem em qualquer lugar do mundo. E por favor não use 'Convert.ToXxxx'. A classe 'Converter' é considerada apenas uma base para conversões em qualquer direção. Além disso: você também pode usar o mecanismo semelhante para o DateTimes.

AndresRohrAtlasInformatik
fonte
Acordado! Tentar implementar manualmente os recursos de Cultura resultará em um caso que você não esperava e uma grande dor de cabeça. Deixe o .NET lidar com isso corretamente.
Khalos
2
um grande problema é quando os usuários estão usando um separador decimal que não é considerado o separador decimal para suas configurações culturais
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)));
Martin
fonte
2

Meus dois centavos neste tópico, tentando fornecer um método genérico de dupla conversão:

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 esperado com:

  • 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

Nenhuma conversão padrão é implementado, por isso seria um fracasso tentando analisar 1.3,14, 1,3.14ou casos semelhantes.

eduherminio
fonte
1
"1.000" pretendido como mil falhará.
precisa saber é o seguinte
1

O código a seguir faz o trabalho em qualquer cenário. É um pouco de análise.

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
fonte
2
1.234.567.890 retornaria 1234567.890
Dan Vogel
Eu não tentei, mas se você executar o aplicativo em SOs de culturas diferentes, esse código não funcionaria, eu acho: /
Dani BISHOP
1

Acho que a conversão 100% correta não é possível se o valor vier de uma entrada do usuário. por exemplo, se o valor for 123.456, pode ser um agrupamento ou um ponto decimal. Se você realmente precisa de 100%, deve descrever seu formato e lançar uma exceção, se não estiver correta.

Mas eu aprimorei o código do JanW, para chegarmos um pouco mais à frente nos 100%. A idéia por trás disso é que, se o último separador for um groupSeperator, esse seria mais um tipo inteiro do que um duplo.

O código adicionado está no primeiro se 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
fonte
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);
Alexander
fonte
0

Em vez de precisar especificar um código de idioma em todas as análises, prefiro definir um código de idioma em todo o aplicativo, embora se os formatos de sequência não sejam consistentes no aplicativo, isso pode não funcionar.

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

Definir isso no início do seu aplicativo fará com que todas as análises duplas esperem uma vírgula como delimitador decimal. Você pode definir um código de idioma apropriado para que o separador decimal e milhares se ajuste às seqüências de caracteres que você está analisando.

Miguel Mesquita Alfaiate
fonte
0

É difícil sem especificar qual separador decimal procurar, mas se você o fizer, é isso que estou 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", ',');
    }

Isso deve funcionar com qualquer cultura. Falha na análise correta de cadeias de caracteres que possui mais de um separador decimal, diferente das implementações que substituem em vez da troca.

osexpert
fonte
0

Melhorei o código de @JanW também ...

Eu preciso dele para formatar os resultados dos instrumentos médicos, e eles também enviam "> 1000", "23.3e02", "350E-02" e "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
fonte
-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
fonte
-3

Eu acho que é a melhor resposta:

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;
}
Endorphinex
fonte
Explicar seu código e fornecer mais detalhes seria útil.
Charlie Fish
O que explicar aqui? Tudo está em comentários
Endorphinex
-3

O abaixo é menos eficiente, mas eu uso essa lógica. Isso é válido apenas se você tiver dois dígitos após o ponto 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));
trabalho especializado
fonte
-5

Multiplique o número e divida-o pelo que você multiplicou antes.

Por exemplo,

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