web-dev-qa-db-fra.com

Meilleure façon de convertir une chaîne en séparateur décimal "." et "," de manière insensible?

L'application traite des chaînes qui représentent des décimales provenant de différentes cultures. Par exemple, "1.1 et" 1,1 "ont la même valeur.

J'ai joué avec Decimal.TryParse marque les combinaisons mais n'a pas pu obtenir le résultat souhaité. "1,1" est devenu "11" ou "0" après tout.

Est-il possible de convertir de telles chaînes en décimal en une seule ligne de code sans avoir à remplacer "," char en "." ou jouer avec NumberFormat.NumberDecimalSeparator?

Comment gérez-vous de telles situations?

Merci d'avance!

20
Andrew Florko

Vous avez les possibilités suivantes:

  1. Vous connaissez la culture
    1. Utiliser le paramètre Culture actuel, pour lequel l'ordinateur est installé
    2. Vous laissez l'utilisateur décider de définir sa culture -> les paramètres utilisateur dans votre programme
  2. Vous ne connaissez pas la culture
    1. Vous devez en décider: vous devez définir et documenter votre décision
    2. Devinez: vous essayez d'analyser, et essayez d'analyser, et essayez de ... jusqu'à ce que vous obteniez des nombres valides
6
OlimilOops

Vous pouvez créer un objet CultureInfo temporaire à utiliser lors de l'analyse.

// get a temporary culture (clone) to modify
var ci = CultureInfo.InvariantCulture.Clone() as CultureInfo;
ci.NumberFormat.NumberDecimalSeparator = ",";
decimal number = decimal.Parse("1,1", ci); // 1.1
33
Jeff Mercado

J'ai trouvé une autre façon de le faire. Ça a l'air bizarre mais ça marche bien pour moi.

Donc, si vous ne connaissez pas la culture du système cible et que vous ne savez pas quelle valeur vous obtiendrez comme 12,33 ou 12,33, vous pouvez le faire en suivant

string amount = "12.33";
// or i.e. string amount = "12,33";

var c = System.Threading.Thread.CurrentThread.CurrentCulture;
var s = c.NumberFormat.CurrencyDecimalSeparator;

amount = amount.Replace(",", s);
amount = amount.Replace(".", s);

decimal transactionAmount = Convert.ToDecimal(amount); 
11
Developer

Vous avez juste besoin d'avoir la bonne culture définie, lorsque vous appelez Parse, comme ceci:

string s = "11,20";

decimal c1 = decimal.Parse(s, new CultureInfo("fr-FR"));
decimal c2 = decimal.Parse(s, new CultureInfo("en-AU"));

Console.WriteLine(c1);
Console.WriteLine(c2);
4
Noon Silk

Ci-dessous mon implémentation, une bonne idée?

/// <summary>
/// 
/// </summary>
public static class NumberExtensions
{
    /// <summary>
    /// Convert string value to decimal ignore the culture.
    /// </summary>
    /// <param name="value">The value.</param>
    /// <returns>Decimal value.</returns>
    public static decimal ToDecimal ( this string value )
    {
        decimal number;
        string tempValue = value;

        var punctuation = value.Where ( x => char.IsPunctuation ( x ) ).Distinct ( );
        int count = punctuation.Count ( );

        NumberFormatInfo format = CultureInfo.InvariantCulture.NumberFormat;
        switch ( count )
        {
            case 0:
                break;
            case 1:
                tempValue = value.Replace ( ",", "." );
                break;
            case 2:
                if ( punctuation.ElementAt ( 0 ) == '.' )
                    tempValue = value.SwapChar ( '.', ',' );
                break;
            default:
                throw new InvalidCastException ( );
        }

        number = decimal.Parse ( tempValue, format );
        return number;
    }
    /// <summary>
    /// Swaps the char.
    /// </summary>
    /// <param name="value">The value.</param>
    /// <param name="from">From.</param>
    /// <param name="to">To.</param>
    /// <returns></returns>
    public static string SwapChar ( this 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 ( );
    }
}

[TestClass]
public class NumberTest
{

    /// <summary>
    /// 
    /// </summary>
    [TestMethod]
    public void Convert_To_Decimal_Test ( )
    {
        string v1 = "123.4";
        string v2 = "123,4";
        string v3 = "1,234.5";
        string v4 = "1.234,5";
        string v5 = "123";
        string v6 = "1,234,567.89";
        string v7 = "1.234.567,89";

        decimal a1 = v1.ToDecimal ( );
        decimal a2 = v2.ToDecimal ( );
        decimal a3 = v3.ToDecimal ( );
        decimal a4 = v4.ToDecimal ( );
        decimal a5 = v5.ToDecimal ( );
        decimal a6 = v6.ToDecimal ( );
        decimal a7 = v7.ToDecimal ( );

        Assert.AreEqual ( ( decimal ) 123.4, a1 );
        Assert.AreEqual ( ( decimal ) 123.4, a2 );
        Assert.AreEqual ( ( decimal ) 1234.5, a3 );
        Assert.AreEqual ( ( decimal ) 1234.5, a4 );
        Assert.AreEqual ( ( decimal ) 123, a5 );
        Assert.AreEqual ( ( decimal ) 1234567.89, a6 );
        Assert.AreEqual ( ( decimal ) 1234567.89, a7 );
    }
    /// <summary>
    /// 
    /// </summary>
    [TestMethod]
    public void Swap_Char_Test ( )
    {
        string v6 = "1,234,567.89";
        string v7 = "1.234.567,89";

        string a1 = v6.SwapChar ( ',', '.' );
        string a2 = v7.SwapChar ( ',', '.' );

        Assert.AreEqual ( "1.234.567,89", a1 );
        Assert.AreEqual ( "1,234,567.89", a2 );
    }
}
3
Andy

Bien, ce n'est toujours pas correct à 100%. lorsque vous utilisez le cas 1: vous supposez automatiquement que "," représente le chiffre décimal. vous devriez au moins vérifier s'il se produit plus d'une fois, car dans ce cas, c'est un symbole de séparation de groupe

            case 1:
                var firstPunctuation = linq.ElementAt(0);
                var firstPunctuationOccurence = value.Where(x => x == firstPunctuation).Count();

                if (firstPunctuationOccurence == 1)
                {
                    // we assume it's a decimal separator (and not a group separator)
                    value = value.Replace(firstPunctuation.ToString(), format.NumberDecimalSeparator);
                }
                else
                {
                    // multiple occurence means that symbol is a group separator
                    value = value.Replace(firstPunctuation.ToString(), format.NumberGroupSeparator);
                }

                break;
2
ajoka

Utilisez TryParse deux fois avec 2 styles qui représentent deux possibilités
Si un seul retourne une valeur, utilisez cette valeur Si les deux renvoient une valeur, utilisez la moindre valeur en termes absolus.

TryParse renverra 0 pour les nombres qui utilisent à la fois le regroupement et le séparateur décimal si vous utilisez le mauvais style, mais si vous n'avez pas de séparateur de regroupement dans votre chaîne (par exemple, un nombre est inférieur à 1000), il renverra de la valeur dans les deux cas mais le "mauvais" nombre sera plus grand (encore une fois en termes absolus)

0