I want to parse a string like "3.5" to a double. However,
double.Parse("3.5")
yields 35 and
double.Parse("3.5", System.Globalization.NumberStyles.AllowDecimalPoint)
throws a FormatException.
Now my computer's locale is set to German, wherein a comma is used as decimal separator. It might have to do something with that and double.Parse() expecting "3,5" as input, but I'm not sure.
How can I parse a string containing a decimal number that may or may not be formatted as specified in my current locale?
double.Parse("3.5", CultureInfo.InvariantCulture)
I usualy use a multi-culture function to parse user input, mostly because if someone is used to the numpad and is using a culture that use a comma as the decimal separator, that person will use the point of the numpad instead of a comma.
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;
}
Beware though, #nikie comments are true. To my defense, I use this function in a controlled environment where I know that the culture can either be en-US, en-CA or fr-CA. I use this function because in French, we use the comma as a decimal separator, but anybody who ever worked in finance will always use the decimal separator on the numpad, but this is a point, not a comma. So even in the fr-CA culture, I need to parse number that will have a point as the decimal separator.
I couldn't write a comment, so I write here:
double.Parse("3.5", CultureInfo.InvariantCulture) is not a good idea, because in Canada we write 3,5 instead of 3.5 and this function gives us 35 as a result.
I tested both on my computer:
double.Parse("3.5", CultureInfo.InvariantCulture) --> 3.5 OK
double.Parse("3,5", CultureInfo.InvariantCulture) --> 35 not OK
This is a correct way that Pierre-Alain Vigeant mentioned
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;
}
Double.Parse("3,5".Replace(',', '.'), CultureInfo.InvariantCulture)
Replace the comma with a point before parsing. Useful in countries with a comma as decimal separator. Think about limiting user input (if necessary) to one comma or point.
Look, every answer above that proposes writing a string replacement by a constant string can only be wrong. Why? Because you don't respect the region settings of Windows! Windows assures the user to have the freedom to set whatever separator character s/he wants. S/He can open up the control panel, go into the region panel, click on advanced and change the character at any time. Even during your program run. Think of this. A good solution must be aware of this.
So, first you will have to ask yourself, where this number is coming from, that you want to parse. If it's coming from input in the .NET Framework no problem, because it will be in the same format. But maybe it was coming from outside, maybe from a external server, maybe from an old DB that only supports string properties. There, the db admin should have given a rule in which format the numbers are to be stored. If you know for example that it will be an US DB with US format you can use this piece of code:
CultureInfo usCulture = new CultureInfo("en-US");
NumberFormatInfo dbNumberFormat = usCulture.NumberFormat;
decimal number = decimal.Parse(db.numberString, dbNumberFormat);
This will work fine anywhere on the world. And please don't use 'Convert.ToXxxx'. The 'Convert' class is thought only as a base for conversions in any direction. Besides: You may use the similar mechanism for DateTimes too.
The trick is to use invariant culture, to parse dot in all cultures.
double.Parse("3.5", System.Globalization.NumberStyles.AllowDecimalPoint, System.Globalization.NumberFormatInfo.InvariantInfo);
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)));
My two cents on this topic, trying to provide a generic, double conversion method:
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;
}
Works as expected with:
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
No default conversion is implemented, so it would fail trying to parse 1.3,14, 1,3.14 or similar cases.
The following code does the job in any scenario. It's a little bit parsing.
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);
}
I think 100% correct conversion isn't possible, if the value comes from a user input. e.g. if the value is 123.456, it can be a grouping or it can be a decimal point. If you really need 100% you have to describe your format and throw an exception if it is not correct.
But I improved the code of JanW, so we get a little bit more ahead to the 100%. The idea behind is, that if the last separator is a groupSeperator, this would be more an integer type, than a double.
The added code is in the first if of 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;
}
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);
Instead of having to specify a locale in all parses, I prefer to set an application wide locale, although if string formats are not consistent across the app, this might not work.
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("pt-PT");
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("pt-PT");
Defining this at the begining of your application will make all double parses expect a comma as the decimal delimiter. You can set an appropriate locale so that the decimal and thousands separator fits the strings you are parsing.
It's difficult without specifying what decimal separator to look for, but if you do, this is what I'm using:
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", ',');
}
This should work with any culture. It correctly fails to parse strings that has more than one decimal separator, unlike implementations that replace instead of swap.
I improved the code of #JanW as well...
I need it to format results from medical instruments, and they also send ">1000", "23.3e02", "350E-02", and "NEGATIVE".
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;
}
}
Here is a solution that handles any number string that many include commas and periods. This solution is particular for money amounts so only the tenths and hundredths place are expected. Anything more is treated as a whole number.
First remove anything that is not a number, comma, period, or negative sign.
string stringAmount = Regex.Replace(originalString, #"[^0-9\.\-,]", "");
Then we split up the number into the whole number and decimal number.
string[] decimalParsed = Regex.Split(stringAmount, #"(?:\.|,)(?=\d{2}$)");
(This Regex expression selects a comma or period that is two numbers from the end of the string.)
Now we take the whole number and strip it of any commas and periods.
string wholeAmount = decimalParsed[0].Replace(",", "").Replace(".", "");
if (wholeAmount.IsNullOrEmpty())
wholeAmount = "0";
Now we handle the decimal part, if any.
string decimalAmount = "00";
if (decimalParsed.Length == 2)
{
decimalAmount = decimalParsed[1];
}
Finally we can put the whole and decimal together and parse the Double.
double amount = $"{wholeAmount}.{decimalAmount}".ToDouble();
This will handle 200,00, 1 000,00 , 1,000 , 1.000,33 , 2,000.000,78 etc.
I am developing a .Net Maui app that runs on Windows, Mac, Android, and iPhone. I have 3 double values that I parse and store using '.' (e.g. "32.5") in all cases: latitude, longitude, and altitude. I happen to have the Android and iPhone set for Spanish and noticed that the Android parsed the '.' string just fine. However, the iPhone refused to parse it correctly unless I substituted ',' for the '.'. Otherwise, the result was always a huge number.
Rather than deal with the complications of localization, I came up with a simple solution that takes advantage of the specific limits of my double numbers.
case "Lat":
waypoint.Lat = ParseDouble(xmlVal, 90);
break;
case "Lon":
waypoint.Lon = ParseDouble(xmlVal, 180);
break;
case "Alt":
waypoint.Alt = ParseDouble(xmlVal, 32000);
public static double ParseDouble(string val, double limit)
{
double result;
if (double.TryParse(val, out result))
{
if (Math.Abs(result) <= limit)
return result;
else if (double.TryParse(val.Replace('.', ','), out result))
{
if (Math.Abs(result) <= limit)
return result;
}
}
return 0;
}
System.Globalization.CultureInfo ci = System.Globalization.CultureInfo.CurrentCulture;
string _pos = dblstr.Replace(".",
ci.NumberFormat.NumberDecimalSeparator).Replace(",",
ci.NumberFormat.NumberDecimalSeparator);
double _dbl = double.Parse(_pos);
The below is less efficient, but I use this logic. This is valid only if you have two digits after decimal point.
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));
Multiply the number and then divide it by what you multiplied it by before.
For example,
perc = double.Parse("3.555)*1000;
result = perc/1000
The problem:
Let's assume you are using a dot "." as a decimal separator in your regional setting and have coded a string with a comma.
string str = "2,5";
What happens when you decimal.TryParse(str, out somevariable); it?
somevariable will assume 0.
What can you do to solve it?
1-
You can
decimal.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out somevariable);
And it will return 25, and not 2.5 which is wrong.
2-
You can
decimal.TryParse(str.Replace(",","."), out num);
And it will return the proper value, BUT, if the user uses "," as a decimal separator it will not work.
Possible solution that I can't make it work:
Get the user decimal separator in regional settings:
char sepdec = Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator);
And make somehow the replace from ",",sepdec , that way it would stay a comma if its a comma, and replace by an actual dot if the user uses dots.
Hints?
Edit: Many users posted useful information, lately, using the arguments NumberStyles.Any, CultureInfo.GetCultureInfo("pt-PT") on a tryParse wouldn't work if your separator is set to "," So it pretty much doesnt fullfill the premise of making a tryparse "universal".
I'll work around this, if anyone has more hints you'r welcome
I know the thread is a little bit older, but I try to provide an answer.
I use regular expression to determine the used number format in the string.
The regex also matches numbers without decimal separators ("12345").
var numberString = "1,234.56"; // en
// var numberString = "1.234,56"; // de
var cultureInfo = CultureInfo.InvariantCulture;
// if the first regex matches, the number string is in us culture
if (Regex.IsMatch(numberString, #"^(:?[\d,]+\.)*\d+$"))
{
cultureInfo = new CultureInfo("en-US");
}
// if the second regex matches, the number string is in de culture
else if (Regex.IsMatch(numberString, #"^(:?[\d.]+,)*\d+$"))
{
cultureInfo = new CultureInfo("de-DE");
}
NumberStyles styles = NumberStyles.Number;
bool isDouble = double.TryParse(numberString, styles, cultureInfo, out number);
HTH
Thomas
I just want to say that HTH Thomas solution worked really well in my project, except for when trying to parse negative decimal numbers with commas. One solution to this, which is probably not optimized because I don't fully understand the regex Ismatch code, but that works is adding the possibility of finding a "-" before the number in the ifs statements, like this:
var cultureInfo = CultureInfo.InvariantCulture;
if (Regex.IsMatch(equation.inputFieldsTexts[i], #"^(:?[\d,]+\.)*\d+$") || Regex.IsMatch(equation.inputFieldsTexts[i], #"^(:?[-\d,]+\.)*\d+$"))
{
cultureInfo = new CultureInfo("en-US");
}
// if the second regex matches, the number string is in DE culture
if (Regex.IsMatch(equation.inputFieldsTexts[i], #"^(:?[\d.]+,)*\d+$") || Regex.IsMatch(equation.inputFieldsTexts[i], #"^(:?[-\d.]+,)*\d+$"))
{
cultureInfo = new CultureInfo("de-DE");
}
The solution I use is to simply show the user what the parsed value is.
I have a custom TextBox control which verifies the input when the control loses focus and such. If the control expects a floating point value (which is a property), then it will try to parse the value entered. If the TryParse succeeds, I display the out value in the control's text.
This way, when a user enters 12.3 the value might change to 123 because in the current culture 12,3 is expected. It's then up to them to decide to correct this.
How about this method:
clean the string from anything else than numbers, dot, comma and negative sign
take the last index of dot or comma
split the clean string and remove all thousands separators from the first part
convert both parts to integer
change the sign of the second part if necessary
add the first part with the second part divided by decimal places
public static bool TryParseDoubleUniversal(this string s, out double result) {
result = 0.0;
if (string.IsNullOrEmpty(s)) return false;
var clean = new string(s.Where(x => char.IsDigit(x) || x == '.' || x == ',' || x == '-').ToArray());
var iOfSep = clean.LastIndexOfAny(new[] { ',', '.' });
var partA = clean.Substring(0, iOfSep).Replace(",", string.Empty).Replace(".", string.Empty);
var partB = clean.Substring(iOfSep + 1);
if (!int.TryParse(partA, out var intA)) return false;
if (!int.TryParse(partB, out var intB)) return false;
if (intA < 0) intB *= -1;
var dp = double.Parse("1".PadRight(partB.Length + 1, '0'));
result = intA + intB / dp;
return true;
}
The question is old but since it was my first hit on Google. And the approach in How to change symbol for decimal point in double.ToString()? seems to be a valid solution you can use the NumberFormatInfo to set the decimal separator like this:
string value = "3,2";
NumberFormatInfo nfi = new NumberFormatInfo();
nfi.NumberDecimalSeparator = ",";
decimal.TryParse(value, NumberStyles.Any, nfi, out decimal dec);
I found a solution, I'm a beginner on this regional and comma-dots theme so if you have comments to improve the understanding of this please be welcome.
We start of by getting what decimal separator the user has set in his regional options outside before the Form{InitializeComponent();} (I want a universal variable that will allow me to correct the code)
char sepdec = Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator);
In the tryParse, to get it to behave universally we will read the dots and commas in the string, and turn them into the decimal separator we defined as sepdec
decimal.TryParse(str.Replace(",",sepdec.ToString()).Replace(".",sepdec.ToString()), out somevariable);
I hope this helps, please comment improvement suggestions!
In Android Xamarin, I ran into the same issue several times. Some solutions worked until the Android got upgraded into a new version, then the problem came out again. So I came with an universal solution, which works fine. I read the numeric input as text, then parse it into decimal with a custom parser.
The custom parser is returning 0 when parsing into decimal is not possible. It does allow input text containing decimal number with either comma or dot, with no group separators:
public static decimal ParseTextToDecimal(string decimalText)
{
if (decimalText == String.Empty) return 0;
string temp = decimalText.Replace(',', '.');
var decText = temp.Split('.');
if (!Int32.TryParse(decText[0], out int integerPart)) return 0;
if (decText.Length == 1) return integerPart;
if (decText.Length == 2)
{
if (!Int32.TryParse(decText[1], out int decimalPart)) return 0;
decimal powerOfTen = 10m;
for (int i = 1; i < decText[1].Length; i++) powerOfTen *= 10;
return integerPart + decimalPart / powerOfTen;
}
return 0; // there were two or more decimal separators, which is a clear invalid input
}