Comment analyser une chaîne avec un point décimal en double?


231

Je veux analyser une chaîne comme "3.5"un double. cependant,

double.Parse("3.5") 

donne 35 et

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

jette a FormatException.

Maintenant, les paramètres régionaux de mon ordinateur sont définis sur l'allemand, où une virgule est utilisée comme séparateur décimal. Il faudra peut-être faire quelque chose avec cela et double.Parse()attendre "3,5"en entrée, mais je ne suis pas sûr.

Comment analyser une chaîne contenant un nombre décimal qui peut ou non être formaté comme spécifié dans mes paramètres régionaux actuels?


La virgule décimale affectera certainement la sortie.
ChrisF

12
N'oubliez pas la méthode double.TryParse (), si elle convient à votre situation.
Kyle Gagnet

Réponses:


414
double.Parse("3.5", CultureInfo.InvariantCulture)

J'aime utiliser la XmlConvertclasse ... avez-vous des idées si c'est mieux, pire et / ou différent que d'utiliser CultureInfo.InvariantCulture?
ChrisW

1
Eh bien, XmlConvertn'est pas vraiment destiné à être utilisé pour analyser une seule valeur double dans le code. Je préfère utiliser double.Parseou Convert.ToDoublequi rendent mon intention évidente.
Mehrdad Afshari

4
Cela signifie doulble.Parse utilise la culture par défaut qui ne peut pas contenir de point comme point décimal ??
Ahmed Said

3
si vous convertissez 12345678.12345678, il se convertit également 12345678.123457
PUG

4
n'a pas fonctionné pour moi: saute les virgules et les retours et int comme double
fnc12

75

J'utilise habituellement une fonction multiculturelle pour analyser les entrées utilisateur, principalement parce que si quelqu'un est habitué au pavé numérique et utilise une culture qui utilise une virgule comme séparateur décimal, cette personne utilisera le point du pavé numérique au lieu d'une virgule.

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

Attention cependant, les commentaires @nikie sont vrais. Pour ma défense, j'utilise cette fonction dans un environnement contrôlé où je sais que la culture peut être en-US, en-CA ou fr-CA. J'utilise cette fonction parce qu'en français, nous utilisons la virgule comme séparateur décimal, mais quiconque ayant déjà travaillé dans la finance utilisera toujours le séparateur décimal sur le pavé numérique, mais c'est un point, pas une virgule. Donc, même dans la culture fr-CA, j'ai besoin d'analyser le nombre qui aura un point comme séparateur décimal.


18
Je ne suis pas sûr que ce soit une bonne idée. Vous ne pouvez pas analyser de manière fiable un double si vous ne connaissez pas la culture: en Allemagne, les valeurs doubles peuvent contenir des «.», Mais ils sont considérés comme des milliers de séparateurs là-bas. Donc, dans le cas de Legate, GetDouble ("3.5") retournerait 35 dans un environnement local allemand et 3.5 dans un environnement en-us.
Niki

Non, Pierre Alain a raison, car c'est exactement comme écrit. Si votre culture dit que "le point vaut mille", alors "3,5" est considéré comme "35" et c'est bien. Cependant, si vous ne cultivez pas de règles pour le "point", le caractère est analysé comme un point décimal et cela fonctionne également. J'aurais évité d'essayer la culture enUS, mais c'est un choix personnel.
xryl669

Si vous utilisez le pavé numérique en culture avec virgule, la touche point sera définie par une virgule.
CrazyBaran

Le séparateur décimal du pavé numérique dépend de la disposition du clavier (et non des paramètres régionaux - au moins sur Windows 7) (j'utilise le hongrois pour écrire du texte, des e-mails ... et en-US pour écrire du code, il peut donc être soit un point ou virgule. J'utilise également des paramètres régionaux personnalisés où j'ai changé le séparateur décimal de ',' en '.' et le séparateur de liste de ';' en ','. Vous savez, cause csv ... Bonne chance à nous tous qui écrivons plusieurs -culture apps;)
Steven Spark

25

Je n'ai pas pu écrire de commentaire, alors j'écris ici:

double.Parse ("3.5", CultureInfo.InvariantCulture) n'est pas une bonne idée, car au Canada, nous écrivons 3,5 au lieu de 3,5 et cette fonction nous donne 35 en conséquence.

J'ai testé les deux sur mon ordinateur:

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

C'est une façon correcte que Pierre-Alain Vigeant a mentionnée

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

1
Re: "... parce qu'au Canada, nous écrivons 3,5 au lieu de 3,5" Êtes-vous sûr de cela? Selon la marque décimale : "Les pays où un point". "Est utilisé comme marque décimale incluent ... Canada (lors de l'utilisation de l'anglais)" . N'est-ce pas plutôt utiliser une version française de Windows?
Peter Mortensen

Baybe à cause de la version française. À Montréal, nous écrivons 3,5 et non 3,5
Malus

Donc, selon votre logique, toujours un seul d'entre eux renvoie vrai?
batmaci

Regardez- le
Malus

Il y a toujours une erreur. Pour la chaîne d'entrée comme GetDouble ("10 ,,,,,,,, 0", 0,0). La fonction mentionnée renvoie 100.
Krivers

21
Double.Parse("3,5".Replace(',', '.'), CultureInfo.InvariantCulture)

Remplacez la virgule par un point avant l'analyse. Utile dans les pays avec une virgule comme séparateur décimal. Pensez à limiter l'entrée utilisateur (si nécessaire) à une virgule ou à un point.


1
Réponse bien plus correcte que celle qui a +133 votes ... Elle permet de vivre sur les deux systèmes avec "," ou "." séparateur décimal ...
Badiboy

@Badiboy pouvez-vous expliquer ce qui ne va pas avec cette réponse? Si je comprends bien, InvariantCulture a toujours ''. comme séparateur décimal. Cela devrait donc fonctionner pour les deux systèmes.
Alex P.

@ Alex11223 Vous avez raison. C'est pourquoi j'ai dit que cette réponse est meilleure que plus populaire. PS: Parler amicalement ce code échouera également si vous avez le "," comme LIST SEPARATOR (ie 1 234 560,01), mais je ne sais pas comment résoudre cela du tout. :)
Badiboy

4
Ce n'est pas une bonne réponse car dans certains cultureinfos le, est le séparateur de milliers et peut être utilisé. Si vous le remplacez par un point, vous finissez par avoir plusieurs points et l'analyse échoue: Double.Parse ((12345.67) .ToString ("N", new CultureInfo ("en")). Replace (',', '. '), CultureInfo.InvariantCulture) car (12345.67) .ToString ("N", new CultureInfo ("en")). Replace (', ','. ') Sera formaté en "12.345.67"
codingdave

1234,56 -> 1,234,56 pas analyseur. une autre idée est de vérifier si le numéro contient '.' et ',' et remplacer ',' par une chaîne vide et si seulement ',' la virgule présentée le remplace par '.'
GDocal

16

L'astuce consiste à utiliser la culture invariante, pour analyser les points dans toutes les cultures.

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

11

Regardez, chaque réponse ci-dessus qui propose d'écrire un remplacement de chaîne par une chaîne constante ne peut être que fausse. Pourquoi? Parce que vous ne respectez pas les paramètres de région de Windows! Windows garantit à l'utilisateur la liberté de définir le caractère de séparation qu'il souhaite. Il peut ouvrir le panneau de contrôle, aller dans le panneau de région, cliquer sur avancé et changer le personnage à tout moment. Même pendant l'exécution de votre programme. Pensez à ça. Une bonne solution doit en être consciente.

Donc, vous devrez d'abord vous demander, d'où vient ce nombre, que vous voulez analyser. Si cela provient d'une entrée dans le .NET Framework, aucun problème, car il sera au même format. Mais peut-être que cela venait de l'extérieur, peut-être d'un serveur externe, peut-être d'une ancienne base de données qui ne prend en charge que les propriétés de chaîne. Là, l'administrateur de la base de données aurait dû donner une règle dans quel format les nombres doivent être stockés. Si vous savez par exemple que ce sera une base de données américaine au format américain, vous pouvez utiliser ce morceau de code:

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

Cela fonctionnera bien partout dans le monde. Et veuillez ne pas utiliser 'Convert.ToXxxx'. La classe 'Convert' est uniquement conçue comme une base pour les conversions dans n'importe quelle direction. En outre: Vous pouvez également utiliser le mécanisme similaire pour DateTimes.


D'accord! Essayer d'implémenter manuellement les fonctionnalités de Culture aboutira finalement à un cas auquel vous ne vous attendiez pas et à un gros mal de tête. Laissez .NET le gérer correctement.
Khalos

2
un gros problème est lorsque les utilisateurs utilisent un séparateur décimal qui n'est pas considéré comme le séparateur décimal pour ses paramètres culturels
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)));

2

Mes deux cents sur ce sujet, en essayant de fournir une méthode générique de double conversion:

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

Fonctionne comme prévu avec:

  • 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

Aucune conversion par défaut est mis en œuvre, il échouerait en essayant d'analyser 1.3,14, 1,3.14ou des cas similaires.


1
"1 000" destiné à mille échouera.
Defkon1

1

Le code suivant fait le travail dans n'importe quel scénario. C'est un peu l'analyse.

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

2
1.234.567.890 retournerait 1234567.890
Dan Vogel

Je n'ai pas essayé, mais si vous exécutez l'application dans des OS de cultures différentes, ce code ne ferait pas l'affaire, je pense: /
Dani bISHOP

1

Je pense qu'une conversion correcte à 100% n'est pas possible, si la valeur provient d'une entrée utilisateur. Par exemple, si la valeur est 123,456, il peut s'agir d'un regroupement ou d'un point décimal. Si vous avez vraiment besoin de 100%, vous devez décrire votre format et lever une exception s'il n'est pas correct.

Mais j'ai amélioré le code de JanW, nous obtenons donc un peu plus d'avance sur le 100%. L'idée derrière est que si le dernier séparateur est un groupSeperator, ce serait plus un type entier qu'un double.

Le code ajouté est dans le premier if 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;
}

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

0

Au lieu d'avoir à spécifier des paramètres régionaux dans toutes les analyses, je préfère définir des paramètres régionaux pour toute l'application, bien que si les formats de chaîne ne sont pas cohérents dans l'application, cela pourrait ne pas fonctionner.

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

Si vous le définissez au début de votre application, toutes les analyses doubles attendent une virgule comme délimiteur décimal. Vous pouvez définir un paramètre régional approprié afin que le séparateur décimal et des milliers corresponde aux chaînes que vous analysez.


0

C'est difficile sans spécifier le séparateur décimal à rechercher, mais si vous le faites, voici ce que j'utilise:

    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", ',');
    }

Cela devrait fonctionner avec n'importe quelle culture. Il échoue correctement à analyser les chaînes qui ont plus d'un séparateur décimal, contrairement aux implémentations qui remplacent au lieu de swap.


0

J'ai également amélioré le code de @JanW ...

J'en ai besoin pour formater les résultats d'instruments médicaux, et ils envoient également "> 1000", "23.3e02", "350E-02" et "NÉGATIF".

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

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

-3

Je pense que c'est la meilleure réponse:

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

Il serait utile d'expliquer votre code et de fournir plus de détails.
Charlie Fish

Que m'expliquer ici? Tout est dans les commentaires
Endorphinex

-3

Ce qui suit est moins efficace, mais j'utilise cette logique. Ceci n'est valable que si vous avez deux chiffres après le point décimal.

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

-5

Multipliez le nombre, puis divisez-le par ce que vous avez multiplié auparavant.

Par exemple,

perc = double.Parse("3.555)*1000;
result = perc/1000
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.