Best way to convert string to decimal separator "." and "," insensitive way?

54,245

Solution 1

You have the following possibllities:

  1. You know the culture
    1. Use the current Culture setting, for which the computer is installed
    2. You let the user decide to set his culture -> user settings in your program
  2. You do not know the culture
    1. You must decide about it: you have to define and document your decision
    2. Guess: you try to parse, and try to parse, and try to ... until you get valid numbers

Solution 2

You can create a temporary CultureInfo object to use when you parse.

// 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

Solution 3

I found another way to do it. It looks odd but it works fine for me.

So if you don't know culture of the target system and you don't know which value you will get like 12.33 or 12,33 you can do following

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

Solution 4

You just need to have the correct culture set, when calling Parse, like so:

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

Solution 5

Below is my implementation, any good idear?

/// <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 );
    }
}
Share:
54,245

Related videos on Youtube

Andrew Florko
Author by

Andrew Florko

in search of sunrise

Updated on July 09, 2022

Comments

  • Andrew Florko
    Andrew Florko almost 2 years

    Application deals with strings that represent decimals that come from different cultures. For example "1.1 and "1,1" is the same value.

    I played with Decimal.TryParse flags combinations but couldn't achieve the result I want. "1,1" became "11" or "0" after all.

    Is it possible to convert such strings to decimal in one line of code without pre-replacing "," char to "." or playing with NumberFormat.NumberDecimalSeparator ?

    How do you handle such situations?

    Thank you in advance!

    • Kobi
      Kobi over 13 years
      How can you be sure what 1,234 means?
    • Andrew Florko
      Andrew Florko over 13 years
      It's the floating point value between 1 and 2 :) "1.234" is the same floating value
    • dlanod
      dlanod over 13 years
      Seconded what Kobi posted first up. 1,234 is one and two-hundred-and-thirty-four thousandths in French but one thousand, two hundred and thirty four in English. If you don't know the source culture (as you commented below), you can't meaningfully determine what was originally meant.
    • JohnG79
      JohnG79 over 6 years
      Regex.Replace(value, @"(?<=\d)[.,](?=\d)", CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSepar‌​ator)
  • Andrew Florko
    Andrew Florko over 13 years
    But I don't know the original culture :(
  • Noon Silk
    Noon Silk over 13 years
    You must ensure that you know the original culture, otherwise you can't decide what the number is meant to indicate.
  • Bertvan
    Bertvan over 13 years
    What if the value is coming from a form? Replace ','s with '.'s before parsing? (edit: okay that was asked not to use, but still...)
  • Kobi
    Kobi over 13 years
    Ok, more seriously - you almost never want to use CurrentCulture on the server - you don't know how it's set.
  • Andrew Florko
    Andrew Florko over 13 years
    How about InvariantCulture with NumberDecimalSeparator override?
  • Jeff Mercado
    Jeff Mercado over 13 years
    @Kobi: The point was to get an instance of a CultureInfo object that could be modifiable without arbitrarily having to choose one. Cloning was necessary since the instance from the static properties were readonly. Though I suppose a clone of the InvariantCulture would have been a better choice.
  • Cheng Chen
    Cheng Chen over 13 years
    @Bertvan: CultureInfo.CurrentCulture provides the default cultureinfo of "your form"(actually it's the cultureinfo of the os)
  • Bertvan
    Bertvan over 13 years
    @Danny Chen: Okay, but I'm a Belgian (nl-be - comma decimals) user with an English (en-us - point decimals) OS. While the actual CultureInfo is know, you never know what a user will enter...
  • Evilripper
    Evilripper about 7 years
    The strings are dangerous, possible problem with thousand separator? 1.2345,40?
  • Jiří Herník
    Jiří Herník about 6 years
    and what about the case, where the comma (,) is there only once, but it's still group separating symbol? eg "111,222" meaning 111222.0