101

I am trying to write a function which basically converts an array of strings to an array of strings where all the doubles in the array are rounded to the number of decimalplaces i set. There can also be strings in the array which are no double values at all.

string[,] values = new string[1, 3];

values[0, 0] = "hello";
values[0, 1] = "0.123";
values[0, 2] = "0,123";

int decimalPlaces = 2;

double tmp;
string format = "F" + decimalPlaces.ToString();
IFormatProvider provider = CultureInfo.InvariantCulture;
for (int i = 0; i < values.GetLength(0); i++)
{
    for (int j = 0; j < values.GetLength(1); j++)
    {
        if (double.TryParse(values[i, j], out tmp))
        {
            values[i, j] = tmp.ToString(format, provider);
        }
    }
}

Console.ReadLine();

The result has to be: "hello" , "0.12", "0.12" but it is "hello", "123.00", "0.12" will treat the comma in the wrong way. Does anyone have a simple and efficient solution for this?

metacircle
  • 2,438
  • 4
  • 25
  • 39
  • 3
    As hultqvist pointed out in a comment, the currently accepted answer breaks if the current culture uses a dot "." as a decimal point! So, would you mind changing the accepted answer to the most upvoted one? – Stefnotch Nov 13 '18 at 16:03

15 Answers15

197

To treat both , and . as decimal point you must not only replace one with the other, but also make sure the Culture used parsing interprets it as a decimal point.

text = text.Replace(',', '.');
return double.TryParse(text, NumberStyles.Any, CultureInfo.InvariantCulture, out value);
hultqvist
  • 17,451
  • 15
  • 64
  • 101
  • 2
    This might lead to some issues with thousand separators. – Retic May 14 '20 at 12:32
  • 1
    @Retic Indeed, what is a thousand separator for one culture is the decimal separator for another. – hultqvist May 15 '20 at 09:54
  • 1
    @hultqvist I presume that in Latin Spanish culture '1,000' will be replaced to '1'. And '1000' was expected. – Alan Mattano Jul 16 '20 at 15:42
  • 4
    @alan-mattano this solution is for a specific problem. There is no single solution for all cultures, you have to first detect which culture is in use and then choose algorithm accordingly. – hultqvist Jul 18 '20 at 04:12
67

You DO NOT NEED to replace the comma and dot..

I have had the very same problem. The reason is simple, the conversion culture plays a big role in which the comma or a dot is interpreted. I use a German culture where the comma distinguish the fractions, where as elsewhere the dot does the job.

Here I made a complete example to make the difference clear.

string[] doubleStrings = {"hello", "0.123", "0,123"};
double localCultreResult;
foreach (var doubleString in doubleStrings)
{
    double.TryParse(doubleString, NumberStyles.Any, CultureInfo.CurrentCulture, out localCultreResult);
    Console.WriteLine(string.Format("Local culture results for the parsing of {0} is {1}", doubleString, localCultreResult));
}

double invariantCultureResult;
foreach (var doubleString in doubleStrings)
{
    double.TryParse(doubleString, NumberStyles.Any, CultureInfo.InvariantCulture, out invariantCultureResult);
    Console.WriteLine(string.Format("Invariant culture results for the parsing of {0} is {1}", doubleString, invariantCultureResult));
}

The results is the following: enter image description here

Play around with the culture and you will get the result you need.

radbyx
  • 9,352
  • 21
  • 84
  • 127
OliverAssad
  • 1,022
  • 11
  • 8
  • I tried this solution on few PCs with different culture settings. It doesnt work as expected. I will post another solution later. – Andrew_STOP_RU_WAR_IN_UA Nov 08 '16 at 03:11
  • I tried this but it doesnt work for me. please see the image in the link. CurrentCulture parameter is German there. https://imgur.com/QvtPt7S – Emil Oct 09 '17 at 00:49
  • 3
    This should be the accepted answer. You can use `NumberStyles.AllowDecimalPoint` as second parameter as well if you need to be more restrictive. That worked fine for me! – Matt Sep 09 '19 at 14:59
27

You want to treat dot (.) like comma (,). So, replace

if (double.TryParse(values[i, j], out tmp))

with

if (double.TryParse(values[i, j].Replace('.', ','), out tmp))
mmdemirbas
  • 9,060
  • 5
  • 45
  • 53
19

The problem is that you (or the system) cannot distinguish a decimal separator from a thousands separator when they can be both a comma or dot. For example:

In my culture,

1.123 is a normal notation for a number above 1000; whereas

1,123 is a number near 1.

Using the invariant culture defaults to using the dot as a decimal separator. In general you should ensure that all numbers are written using the same constant culture on all systems (e.g. the invariant culture).

If you are sure that your numbers never contain anything other than a comma or dot for a decimal separator (i.e. no thousands separators), I'd String.Replace() the comma with a dot and do the rest as you did.

Otherwise, you'll have a hard time programming something that can distinguish 1.123 from 1,123 without knowing the culture.

Community
  • 1
  • 1
Daniel A.A. Pelsmaeker
  • 47,471
  • 20
  • 111
  • 157
  • Agreed, for me this is the most logical solution. You could also check if the strings contain a "," or "." but either one of these solutions should be fine imo. – eandersson Jul 19 '12 at 12:15
  • 1
    "... on all systems ..." is far beyond my span of control. – R. Schreurs Oct 12 '18 at 07:55
7

Make two static cultures, one for comma and one for point.

    var commaCulture = new CultureInfo("en")
    {
        NumberFormat =
        {
            NumberDecimalSeparator = ","
        }
    };

    var pointCulture = new CultureInfo("en")
    {
        NumberFormat =
        {
            NumberDecimalSeparator = "."
        }
    };

Then use each one respectively, depending on the input (using a function):

    public double ConvertToDouble(string input)
    {
        input = input.Trim();

        if (input == "0") {
            return 0;
        }

        if (input.Contains(",") && input.Split(',').Length == 2)
        {
            return Convert.ToDouble(input, commaCulture);
        }

        if (input.Contains(".") && input.Split('.').Length == 2)
        {
            return Convert.ToDouble(input, pointCulture);
        }

        throw new Exception("Invalid input!");
    }

Then loop through your arrays

    var strings = new List<string> {"0,12", "0.122", "1,23", "00,0", "0.00", "12.5000", "0.002", "0,001"};
    var doubles = new List<double>();

    foreach (var value in strings) {
        doubles.Add(ConvertToDouble(value));
    }

This should work even though the host environment and culture changes.

Sinipelto
  • 147
  • 1
  • 8
6

Simple use:

double.Parse("3.5", CultureInfo.InvariantCulture)
Syscall
  • 19,327
  • 10
  • 37
  • 52
Zsolt
  • 61
  • 1
  • 1
  • 1
    Although this works, it is important to explain why. Please read this S.O. post for understanding what the `InvariantCulture` is: https://stackoverflow.com/questions/9760237/what-does-cultureinfo-invariantculture-mean – sɐunıɔןɐqɐp Mar 30 '21 at 10:41
2

Use this overload of double.TryParse to specify allowed formats:

Double.TryParse Method (String, NumberStyles, IFormatProvider, Double%)

By default, double.TryParse will parse based on current culture specific formats.

mmdemirbas
  • 9,060
  • 5
  • 45
  • 53
Paul Fleming
  • 24,238
  • 8
  • 76
  • 113
1

Extension to parse decimal number from string.

  • No matter number will be on the beginning, in the end, or in the middle of a string.
  • No matter if there will be only number or lot of "garbage" letters.
  • No matter what is delimiter configured in the cultural settings on the PC: it will parse dot and comma both correctly.
  • Ability to set decimal symbol manually.

    public static class StringExtension
    {
        public static double DoubleParseAdvanced(this string strToParse, char decimalSymbol = ',')
        {
            string tmp = Regex.Match(strToParse, @"([-]?[0-9]+)([\s])?([0-9]+)?[." + decimalSymbol + "]?([0-9 ]+)?([0-9]+)?").Value;
    
            if (tmp.Length > 0 && strToParse.Contains(tmp))
            {
                var currDecSeparator = System.Windows.Forms.Application.CurrentCulture.NumberFormat.NumberDecimalSeparator;
    
                tmp = tmp.Replace(".", currDecSeparator).Replace(decimalSymbol.ToString(), currDecSeparator);
    
                return double.Parse(tmp);
            }
    
            return 0;
        }
    }
    

How to use:

"It's 4.45 O'clock now".DoubleParseAdvanced(); // will return 4.45
"It's 4,45 O'clock now".DoubleParseAdvanced(); // will return 4.45
"It's 4:45 O'clock now".DoubleParseAdvanced(':'); // will return 4.45
Andrew_STOP_RU_WAR_IN_UA
  • 9,318
  • 5
  • 65
  • 101
0

I use the following snippet if I want to parse a number with a decimal separator and nothing else:

public bool TryParseDecimal(string value, out decimal result) {
    const string your_separator = ",";

    var numberFormat = new NumberFormatInfo {
            NumberDecimalSeparator = your_separator
    };

    return decimal.TryParse(value, NumberStyles.AllowDecimalPoint, numberFormat, out result);
}

I don't think that using a culture or string manipulation expresses the intention of convertig a number with a non '.' decimal point.

Rüdiger
  • 1,674
  • 1
  • 20
  • 28
0

For me using the culture info was not an option, since the app was running on systems with different cultures but the input strings to be parsed where of unknown culture.

So I referred to normalizing the string representation and then converting it using CultureInfo.InvariantCulture:

    private static double NormalizeAndParse(string strDouble)
    {
        string strDoubleNormalized;

        if (strDouble.Contains(","))
        {
            var strReplaced = strDouble.Replace(",", ".");
            var decimalSeparatorPos = strReplaced.LastIndexOf('.');
            var strInteger = strReplaced.Substring(0, decimalSeparatorPos);
            var strFractional = strReplaced.Substring(decimalSeparatorPos);

            strInteger = strInteger.Replace(".", string.Empty);
            strDoubleNormalized = strInteger + strFractional;
        }
        else
        {
            strDoubleNormalized = strDouble;
        }

        return Double.Parse(strDoubleNormalized, NumberStyles.Any, CultureInfo.InvariantCulture);
    }
Krombir
  • 113
  • 1
  • 5
-1

You can check if the string contains a decimal point using

string s="";

        if (s.Contains(','))
        { 
        //treat as double how you wish
        }

and then treat that as a decimal, otherwise just pass the non-double value along.

Axxelsian
  • 787
  • 3
  • 9
  • 25
-1

Another option would be to use the information on the decimal separator from CultureInfo class. Knowing that we can replace ',' with '.' or vice versa when needed. A number grouping symbol is also available in this class if something needs to be done with a number like 1,000,000.23.

string decimalSeparator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
        string value = "";

        if (decimalSeparator == ".")
        {
            value = rateLimitTextBox.Text.Replace(",", ".");
        }
        else if (decimalSeparator == ",")
        {
            value = rateLimitTextBox.Text.Replace(".", ",");
        }

        bool LimitAcceptable = decimal.TryParse(value, NumberStyles.Any, CultureInfo.CurrentCulture, out decimal limit);
Karol
  • 177
  • 1
  • 6
-1

First of all, I would like to thank Sinipelto.

Here is just a small improvements of his answer

First I decide to declare cultural as static, and took in consideration case when you have value without comma or dot. Hope this saves few seconds for someone

        public static CultureInfo pointCulture = new CultureInfo("en")
    {
        NumberFormat =
            {
                NumberDecimalSeparator = "."
            }
    };

    public static CultureInfo commaCulture = new System.Globalization.CultureInfo("en")
    {
        NumberFormat =
            {
                NumberDecimalSeparator = ","
            }
    };

    private double ConvertToDouble(string input)
    {
        input = input.Trim();

        if (!input.Contains(",") && !input.Contains(".")) 
        {
            return double.Parse(input);
        }

        if (input.Contains(",") && input.Split(',').Length == 2)
        {
            return Convert.ToDouble(input, commaCulture);
        }

        if (input.Contains(".") && input.Split('.').Length == 2)
        {
            return Convert.ToDouble(input, pointCulture);
        }

        throw new Exception("Invalid input!");
    }
Mikhail Kh
  • 176
  • 1
  • 8
-1

This code might help you to convert string to double even if there could be some comma instead of point.

//Get all culture separators

var culutres = CultureInfo.GetCultures(CultureTypes.AllCultures);
var _separators = culutres.Select(x => x.NumberFormat.NumberDecimalSeparator).ToArray();
var separators = new List<string>();
foreach(var separator in _separators)
    if (!separators.Contains(separator))
        separators.Add(separator);

string unparsedText = "900,93";

//Replace all possible separators with point
separators.ForEach(x => unparsedText = unparsedText.Replace(x, "."));

var value = double.Parse(unparsedText, NumberStyles.Number, CultureInfo.InvariantCulture);
Snail
  • 57
  • 7
  • There are cultures that use "." as 1000-separator and "," as decimal separator. So just replacing "," by "." won't cut it. – Fildor Aug 01 '23 at 07:43
  • @Fildor-standswithMods Yeah it's true in case when user makes his own input. But in case if you already have some decimal value (int, float, double etc) and you parse it to string with some Culture it's works. Cause the parser by default do not use the 1000-separators. – Snail Aug 02 '23 at 05:28
-2

try this... it works for me.

double vdouble = 0;
string sparam = "2,1";

if ( !Double.TryParse( sparam, NumberStyles.Float, CultureInfo.InvariantCulture, out vdouble ) )
{
    if ( sparam.IndexOf( '.' ) != -1 )
    {
        sparam = sparam.Replace( '.', ',' );
    }
    else
    {
        sparam = sparam.Replace( ',', '.' );
    }

    if ( !Double.TryParse( sparam, NumberStyles.Float, CultureInfo.InvariantCulture, out vdouble ) )
    {
        vdouble = 0;
    }
}