tags:

views:

82

answers:

3

In the environment that my program is going to run, people use ',' and '.' as decimal separators randomly on PCs with ',' and '.' separators.

How would you implements such a floatparse(string) function?

I tried this one:

    try
    {
        d = float.Parse(s);
    }
    catch
    {
        try
        {
            d = float.Parse(s.Replace(".", ","));
        }
        catch
        {
            d = float.Parse(s.Replace(",", "."));
        }
    }

It doesn't work. And when I debugg it turns out that it parses it wrong the first time thinking that "." is a separator for thousands (like 100.000.000,0).

I'm noob at C#, so hopefully there is less overcomplicated solution then that :-)

NB: People a going to use both '.' and ',' in PCs with different separator settings.

+4  A: 

If you are sure nobody uses thousand-separators, you can Replace first:

string s = "1,23";  // or s = "1.23";

s = s.Replace(',', '.');
double d = double.Parse(s, CultureInfo.InvariantCulture);

The general pattern would be:

  • first try to sanitize and normalize. Like Replace(otherChar, myChar).
  • try to detect a format, maybe using RegEx, and use a targeted conversion. Like counting . and , to guess whether thousand separators are used.
  • try several formats in order, with TryParse. Do not use exceptions for this.
Henk Holterman
as I understand in Invariant culture separator is always '.' ?
Halst
hm, VS2008EE sais 'CultureInfo' does not exist in current context... do I need to declare it somehow?
Halst
You need to import System.Globalization
Filip Ekberg
thanks, that works! do you know if it is possible to set culture as InvariantCulture globally in a program?
Halst
You can't set InvariantCulture for a thread. You need to pass it into the `Single.TryParse()` method as the `IFormatProvider` parameter.
codekaizen
@Halst: Invariant is just a convenient choice for this, but you could pick another fixed culture.
Henk Holterman
A: 

I would do someting like this

float ConvertToFloat(string value)
{
    float result;

    var converted  = float.TryParse(value, out result);

    if (converted) return result;

    converted = float.TryParse(value.Replace(".", ",")), 
                               out result);

    if (converted) return result;

    return float.NaN;
}

In this case the following would return correct data

        Console.WriteLine(ConvertToFloat("10.10").ToString());
        Console.WriteLine(ConvertToFloat("11,0").ToString());
        Console.WriteLine(ConvertToFloat("12").ToString());
        Console.WriteLine(ConvertToFloat("1 . 10").ToString());

Returns

10,1
11
12
NaN

In this case if it is not possible to convert it, you will at least know that it is not a number. It's a safe way to convert.

You can also use the following overload

float.TryParse(value,
            NumberStyles.Currency,
            CultureInfo.CurrentCulture,
            out result)

On this test-code:

Console.WriteLine(ConvertToFloat("10,10").ToString());
Console.WriteLine(ConvertToFloat("11,0").ToString());
Console.WriteLine(ConvertToFloat("12").ToString());
Console.WriteLine(ConvertToFloat("1 . 10").ToString());
Console.WriteLine(ConvertToFloat("100.000,1").ToString());

It returns the following

10,1
11
12
110
100000,1

So depending on how "nice" you want to be to the user, you can always replace the last step, if it is not a number, try converting it this way aswell, otherwsie it really isn't a number.

It would the look like this

float ConvertToFloat(string value)
{
    float result;

    var converted = float.TryParse(value,
                                    out result);


    if (converted) return result;

    converted = float.TryParse(value.Replace(".", ","),
                                out result);

    if (converted) return result;

    converted = float.TryParse(value,
                                    NumberStyles.Currency,
                                    CultureInfo.CurrentCulture,
                                    out result);

    return converted ? result : float.NaN;
}

Where the following

Console.WriteLine(ConvertToFloat("10,10").ToString());
Console.WriteLine(ConvertToFloat("11,0").ToString());
Console.WriteLine(ConvertToFloat("12").ToString());
Console.WriteLine(ConvertToFloat("1 . 10").ToString());
Console.WriteLine(ConvertToFloat("100.000,1").ToString());
Console.WriteLine(ConvertToFloat("asdf").ToString());

Returns

10,1
11
12
110
100000,1
NaN
Filip Ekberg
on PCs with european format 'var converted' will be always true becouse it ignores '.' since it thinks that '.' is thousands separator (100.000.000,0)
Halst
My computer is European and 100.000.000,0 doesn't work at all. You can set the second parameter NumberStyles and the CultureInfo aswell.
Filip Ekberg
ok, maybe it's just something special about (my) danish PC 'culture', but [var converted = float.TryParse(value, out result); ] gives 123456 when input was 123.456 and returns true
Halst
You can set the two additional parameters to fix that.
Filip Ekberg
A: 

Parsing uses the settings of the CultureInfo.CurrentCulture, which reflects the language and the Number format selected by the user through Regional Settings. If your users type the decimals that correspond to the language they chose for their computers, you should have no problem using plain old double.Parse(). If a user sets his locale to Greek and types "8,5", double.Parse("8,5") will return 8.5. If he types "8.5" parse will return 85.

If a user sets his locale to one setting and then starts using the wrong decimal, you face a problem. There is no clean way to separate such wrong entries instead of entries that really wanted to enter the grouping character. What you can do is to warn the user when a number is too short to include a grouping character and use Masked or numerical text boxes to prevent wrong entries.

Another, somewhat stricter option, is to disallow the grouping character for number entry in your application by cancelling it in the KeyDown event of your textboxes. You can get the numeric and grouping characters from CultureInfo.CurrentCulture.NumberFormat property.

Trying to replace the decimal and grouping characters is doomed to fail as it depends on knowing during compile time what kind of separator the user is going to use. If you knew that, you could just parse the number using the CultureInfo in the user's mind. Unfortunately, User.Brain.CultureInfo is not yet part of the .NET framework :P

Panagiotis Kanavos