Convert Unicode string made up of culture-specific digits to integer value - c#

I am developing a program in the Marathi language. In it, I want to add/validate numbers entered in Marathi Unicode by getting their actual integer value.
For example, in Marathi:
४५ = 45
९९ = 99
How do I convert this Marathi string "४५" to its actual integer value i.e. 45?
I googled a lot, but found nothing useful. I tried using System.Text.Encoding.Unicode.GetString() to get string and then tried to parse, but failed here also.

Correct way would be to use Char.GetNumericValue that lets you to convert individual characters to corresponding numeric values and than construct complete value. I.e. Char.GetNumericValue('९') gives you 9.
Depending on your goal it may be easier to replace each national digit character with corresponding invariant digit and use regular parsing functions.
Int32.Parse("९९".Replace("९", "9"))

Quick hack of #Alexi's response.
public static double ParseValue(string value)
{
return double.Parse(string.Join("",
value.Select(c => "+-.".Contains(c)
? "" + c: "" + char.GetNumericValue(c)).ToArray()),
NumberFormatInfo.InvariantInfo);
}
calling ParseValue("१२३.३२१") yields 123.321 as result

I found my solution...
The following code will convert given Marathi number to its equivalent Latin number..
Thanks to #Alexei, I just changed some of your code and its working fine..
string ToLatinDigits(string nativeDigits)
{
int n = nativeDigits.Length;
StringBuilder latinDigits = new StringBuilder(capacity: n);
for (int i = 0; i < n; ++i)
{
if (char.IsDigit(nativeDigits, i))
{
latinDigits.Append(char.GetNumericValue(nativeDigits, i));
}
else if (nativeDigits[i].Equals('.') || nativeDigits[i].Equals('+') || nativeDigits[i].Equals('-'))
{
latinDigits.Append(nativeDigits[i]);
}
else
{
throw new Exception("Invalid Argument");
}
}
return latinDigits.ToString();
}
This method is working for both + and - numbers.
Regards Guruprasad

Windows.Globalization.DecimalFormatter will parse different numeral systems in addition to Latin, including Devanagari (which is what is used by Marathi).

Related

C# string to Decimal On All style or Culture

Hi I want to find if there is any better way to parse the string to Decimal which covers various format
$1.30
£1.50
€2,50
2,50  €
2.500,00  €
I see a lot of examples using culture to convert . & ,. But in my case, I don't have anything to identify the culture.
This display field I get from the client and I need to extract the value.
I tried following (which didn't work for all scenario) but would like to know if we have any best way to handle this.
Decimal.Parse(value,NumberStyles.Currency |
NumberStyles.Number|NumberStyles.AllowThousands |
NumberStyles.AllowTrailingSign | NumberStyles.AllowCurrencySymbol)
I also tried to use Regex to remove the currency sign but unable to convert both 1.8 or 1,8 in one logic.
Well, assuming you always get a valid currency format, and it's only the culture that changes, you could guess which character is used as a decimal point and which is used as a thousands separator by checking which appears the last in the number. Then remove all the thousand separators and parse it like its culture was invariant.
The code would look like the following:
// Replace with your input
var numberString = "2.500,00 €";
// Regex to extract the number part from the string (supports thousands and decimal separators)
// Simple replace of all non numeric and non ',' '.' characters with nothing might suffice as well
// Depends on the input you receive
var regex = new Regex"^[^\\d-]*(-?(?:\\d|(?<=\\d)\\.(?=\\d{3}))+(?:,\\d+)?|-?(?:\\d|(?<=\\d),(?=\\d{3}))+(?:\\.\\d+)?)[^\\d]*$");
char decimalChar;
char thousandsChar;
// Get the numeric part from the string
var numberPart = regex.Match(numberString).Groups[1].Value;
// Try to guess which character is used for decimals and which is used for thousands
if (numberPart.LastIndexOf(',') > numberPart.LastIndexOf('.'))
{
decimalChar = ',';
thousandsChar = '.';
}
else
{
decimalChar = '.';
thousandsChar = ',';
}
// Remove thousands separators as they are not needed for parsing
numberPart = numberPart.Replace(thousandsChar.ToString(), string.Empty);
// Replace decimal separator with the one from InvariantCulture
// This makes sure the decimal parses successfully using InvariantCulture
numberPart = numberPart.Replace(decimalChar.ToString(),
CultureInfo.InvariantCulture.NumberFormat.CurrencyDecimalSeparator);
// Voilá
var result = decimal.Parse(numberPart, NumberStyles.AllowDecimalPoint | NumberStyles.Number, CultureInfo.InvariantCulture);
It does look a bit of complicated for a simple decimal parsing, but I think should do the work for all the input numbers you get or at least the most of them.
If you do this in some sort of loop, you might want to use compiled regex.
The problem here is that in one case . means decimal point but in other it is a thousnads separator. And then you have , as decimal separator. Clearly, it is impossible for the parser to "guess" what is meant, so the only thing you can do is to decide on some rules on how to handle which case.
If you have control over the UI the best approach would be to validate user input and just reject any value that can't be parsed with an explanation on which format is expected.
If you have no control over the UI, the second best option would be to check for some "rules" and then devise which culture is appropriate for that given input and try to run it through decimal.TryParse for that given culture.
For the given input you have, you could have the following rules:
input.StartsWith("$") -> en-US
input.StartsWith("£") -> en-GB
input.StartsWith("€") || input.EndsWith("€") -> de-DE
These could reasonably handle all cases.
In code:
static void Main(string[] args)
{
string[] inputs =
{
"$1.30",
"£1.50",
"€2,50",
"2,50 €",
"2.500,00 €"
};
for (int i = 0; i < inputs.Length; i++)
{
Console.Write((i + 1).ToString() + ". ");
if (decimal.TryParse(inputs[i], NumberStyles.Currency,
GetAppropriateCulture(inputs[i]), out var parsed))
{
Console.WriteLine(parsed);
}
else
{
Console.WriteLine("Can't parse");
}
}
}
private static CultureInfo GetAppropriateCulture(string input)
{
if (input.StartsWith("$"))
return CultureInfo.CreateSpecificCulture("en-US");
if (input.StartsWith("£"))
return CultureInfo.CreateSpecificCulture("en-GB");
if (input.StartsWith("€") || input.EndsWith("€"))
return CultureInfo.CreateSpecificCulture("de-DE");
return CultureInfo.InvariantCulture;
}
Output:
1.30
1.50
2.50
2.50
2500.00
The only way you could do that is just strip string from symbols and change . and , to decimal separator. Something like:
public decimal UniversalConvertDecimal(string str)
{
char currentDecimalSeparator = Convert.ToChar(Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator);
str = str.Replace('.', currentDecimalSeparator);
str = str.Replace(',', currentDecimalSeparator);
StringBuilder builder = new StringBuilder(str.Length);
foreach(var ch in str)
{
if(Char.IsDigit(ch) || ch == currentDecimalSeparator)
builder.Add(ch);
}
string s = builder.ToString();
return Convert.ToDecimal(s);
}
First you have to get current decimal separator from your system.
Then you have to replace . and , with current decimal separator.
Next, you will have to strip the string from any other char than a digit or decimal separator. At the end you can be sure that Convert.ToDecimal is going to work. But I don't know if it is something you want to achieve.
If you need some mechanism to save currency to database, there is a far simpler solution. Just convert this currency to least currency part. For example instead of $1, save 100 cents.
So if you have $1.99, just multiply it by 100 and you will get: 199 cents. And this integer can be saved to db.

decimal.TryParse is happily accepting badly formatted number strings

Is there a way to make the C# TryParse() functions a little more... strict ?
Right now, if you pass in a string containing numbers, the correct decimal & thousand separator characters, it often just seems to accept them, even if the format doesn't make sense, eg: 123''345'678
I'm looking for a way to make TryParse not be successful if the number isn't in the right format.
So, I'm based in Zurich, and if I do this:
decimal exampleNumber = 1234567.89m;
Trace.WriteLine(string.Format("Value {0} gets formatted as: \"{1:N}\"", exampleNumber, exampleNumber));
...then, with my regional settings, I get this...
Value 1234567.89 gets formatted as: "1'234'567.89"
So you can see that, for my region, the decimal place character is a full-stop and the thousand-separator is an apostrophe.
Now, let's create a simple function to test whether a string can be parsed into a decimal:
private void ParseTest(string str)
{
decimal val = 0;
if (decimal.TryParse(str, out val))
Trace.WriteLine(string.Format("Parsed \"{0}\" as {1}", str, val));
else
Trace.WriteLine(string.Format("Couldn't parse: \"{0}\"", str));
}
Okay, let's call this function with a few strings.
Which of the following strings would you think would get successfully parsed by this function ?
Below are the results I got:
ParseTest("123345.67"); // 1. Parsed "123345.67" as 123345.67
ParseTest("123'345.67"); // 2. Parsed "123'345.67" as 123345.67
ParseTest("123'345'6.78"); // 3. Parsed "123'345'6.78" as 1233456.78
ParseTest("1''23'345'678"); // 4. Parsed "1''23'345'678" as 123345678
ParseTest("'1''23'345'678"); // 5. Couldn't parse: "'1''23'345'678"
ParseTest("123''345'678"); // 6. Parsed "123''345'678" as 123345678
ParseTest("123'4'5'6.7.89"); // 7. Couldn't parse: "123'4'5'6.7.89"
ParseTest("'12'3'45'678"); // 8. Couldn't parse: "'12'3'45'678"
I think you can see my point.
To me, only the first two strings should've parsed successfully. The others should've all failed, as they don't have 3-digits after a thousand separator, or have two apostrophes together.
Even if I change the ParseTest to be a bit more specific, the results are exactly the same. (For example, it happily accepts "123''345'678" as a valid decimal.)
private void ParseTest(string str)
{
decimal val = 0;
var styles = (NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands);
if (decimal.TryParse(str, styles, CultureInfo.CurrentCulture, out val))
Trace.WriteLine(string.Format("Parsed \"{0}\" as {1}", str, val));
else
Trace.WriteLine(string.Format("Couldn't parse: \"{0}\"", str));
}
So, is there a straightforward way to not allow badly formatted strings to be accepted by TryParse ?
Update
Thanks for all of the suggestions.
Perhaps I should clarify: what I'm looking for is for the first two of these strings to be valid, but the third one to be rejected.
ParseTest("123345.67");
ParseTest("123'456.67");
ParseTest("12'345'6.7");
Surely there must be a way to use "NumberStyles.AllowThousands" so it can optionally allow thousand-separators but make sure the number format does make sense ?
Right now, if I use this:
if (decimal.TryParse(str, styles, CultureInfo.CurrentCulture, out val))
I get these results:
Parsed "123345.67" as 123345.67
Parsed "123'456.67" as 123456.67
Parsed "12'345'6.7" as 123456.7
And if I use this:
if (decimal.TryParse(str, styles, CultureInfo.InvariantCulture, out val))
I get these results:
Parsed "123345.67" as 123345.67
Couldn't parse: "123'456.67"
Couldn't parse: "12'345'6.7"
This is my problem... regardless of CultureInfo settings, that third string should be rejected, and the first two accepted.
The easiest way to tell if it is correctly formatted based on the current culture would be to compare the resulting number after formatting with the original string.
//input = "123,456.56" -- true
//input = "123,4,56.56" -- false
//input = "123456.56" -- true
//input = "123,,456.56" -- false
string input = "123456.56";
decimal value;
if(!decimal.TryParse(input, out value))
{
return false;
}
return (value.ToString("N") == input || value.ToString() == input);
This will succeed for inputs that completely omit thousand separators and inputs that specify correct thousand separators.
If you need it to accept a range of decimal places then you would need to grab the number of characters after the decimal separator and append it to the "N" format string.
Putting together all the useful suggestions here, here's what I ended up using.
It's not perfect, but, for my corporate app, it does at least reject numeric-strings which "don't look right".
Before I present my code, here's the differences between what my TryParseExact function will accept, and what the regular decimal.TryParse would accept:
And here's my code.
I'm sure there's a more efficient way of doing some of this, using regex or something, but this is sufficient for my needs, and I hope it helps other developers:
public static bool TryParseExact(string str, out decimal result)
{
// The regular decimal.TryParse() is a bit rubbish. It'll happily accept strings which don't make sense, such as:
// 123'345'6.78
// 1''23'345'678
// 123''345'678
//
// This function does the same as TryParse(), but checks whether the number "makes sense", ie:
// - has exactly zero or one "decimal point" characters
// - if the string has thousand-separators, then are there exactly three digits inbetween them
//
// Assumptions: if we're using thousand-separators, then there'll be just one "NumberGroupSizes" value.
//
// Returns True if this is a valid number
// False if this isn't a valid number
//
result = 0;
if (str == null || string.IsNullOrWhiteSpace(str))
return false;
// First, let's see if TryParse itself falls over, trying to parse the string.
decimal val = 0;
if (!decimal.TryParse(str, out val))
{
// If the numeric string contains any letters, foreign characters, etc, the function will abort here.
return false;
}
// Note: we'll ONLY return TryParse's result *if* the rest of the validation succeeds.
CultureInfo culture = CultureInfo.CurrentCulture;
int[] expectedDigitLengths = culture.NumberFormat.NumberGroupSizes; // Usually a 1-element array: { 3 }
string decimalPoint = culture.NumberFormat.NumberDecimalSeparator; // Usually full-stop, but perhaps a comma in France.
string thousands = culture.NumberFormat.NumberGroupSeparator; // Usually a comma, but can be apostrophe in European locations.
int numberOfDecimalPoints = CountOccurrences(str, decimalPoint);
if (numberOfDecimalPoints != 0 && numberOfDecimalPoints != 1)
{
// You're only allowed either ONE or ZERO decimal point characters. No more!
return false;
}
int numberOfThousandDelimiters = CountOccurrences(str, thousands);
if (numberOfThousandDelimiters == 0)
{
result = val;
return true;
}
// Okay, so this numeric-string DOES contain 1 or more thousand-seperator characters.
// Let's do some checks on the integer part of this numeric string (eg "12,345,67.890" -> "12,345,67")
if (numberOfDecimalPoints == 1)
{
int inx = str.IndexOf(decimalPoint);
str = str.Substring(0, inx);
}
// Split up our number-string into sections: "12,345,67" -> [ "12", "345", "67" ]
string[] parts = str.Split(new string[] { thousands }, StringSplitOptions.None);
if (parts.Length < 2)
{
// If we're using thousand-separators, then we must have at least two parts (eg "1,234" contains two parts: "1" and "234")
return false;
}
// Note: the first section is allowed to be upto 3-chars long (eg for "12,345,678", the "12" is perfectly valid)
if (parts[0].Length == 0 || parts[0].Length > expectedDigitLengths[0])
{
// This should catch errors like:
// ",234"
// "1234,567"
// "12345678,901"
return false;
}
// ... all subsequent sections MUST be 3-characters in length
foreach (string oneSection in parts.Skip(1))
{
if (oneSection.Length != expectedDigitLengths[0])
return false;
}
result = val;
return true;
}
public static int CountOccurrences(string str, string chr)
{
// How many times does a particular string appear in a string ?
//
int count = str.Length - str.Replace(chr, "").Length;
return count;
}
Btw, I created the table image above in Excel, and noticed that it's actually hard to paste values like this into Excel:
1'234567.89
Does Excel complain above this value, or try to store it as text ? Nope, it also happily accepts this as a valid number, and pastes it as "1234567.89".
Anyway, job done.. thanks to everyone for their help & suggestions.
It's because parsing just skips the NumberFormatInfo.NumberGroupSeparator string and completely ignores the NumberFormatInfo.NumberGroupSizes property. However, you can implement such a validation:
static bool ValidateNumberGroups(string value, CultureInfo culture)
{
string[] parts = value.Split(new string[] { culture.NumberFormat.NumberGroupSeparator }, StringSplitOptions.None);
foreach (string part in parts)
{
int length = part.Length;
if (culture.NumberFormat.NumberGroupSizes.Contains(length) == false)
{
return false;
}
}
return true;
}
It's still not completely perfect, as the MSDN says:
The first element of the array defines the number of elements in the least significant group of digits immediately to the left of the NumberDecimalSeparator. Each subsequent element refers to the next significant group of digits to the left of the previous group. If the last element of the array is not 0, the remaining digits are grouped based on the last element of the array. If the last element is 0, the remaining digits are not grouped.
For example, if the array contains { 3, 4, 5 }, the digits are grouped similar to "55,55555,55555,55555,4444,333.00". If the array contains { 3, 4, 0 }, the digits are grouped similar to "55555555555555555,4444,333.00".
But you can see the point now.

How to change 1 to 00001?

I want to have numbers with a fixed digit count.
example: 00001, 00198, 48484
I can do like this:
string value;
if (number < 10)
{
value = "0000" + number.ToString();
}
else if (number < 100)
{
value = "000" + number.ToString();
}
else if (number < 1000)
{
...
}
But this is a bit odd. Is there any built in function for my purpose?
Yes, there is:
string value = String.Format("{0:D5}", number);
According to the MS reference: http://msdn.microsoft.com/en-us/library/dd260048.aspx
You can pad an integer with leading zeros by using the "D" standard
numeric format string together with a precision specifier. You can pad
both integer and floating-point numbers with leading zeros by using a
custom numeric format string.
So:
To display the integer as a decimal value, call its ToString(String)
method, and pass the string "Dn" as the value of the format parameter,
where n represents the minimum length of the string.
Code:
string value = number.ToString("D5");
.NET fiddle: http://dotnetfiddle.net/0U9A6N
You should use the ToString() method with custom formating - see the docs. In particular the 0 specifier.
Replaces the zero with the corresponding digit if one is present; otherwise, zero appears in the result string.
eg,
value = number.Tostring("00000");
string value = number.ToString("00000");
You can do it this way :
number.ToString("00000")
If you wish to return 5 digits numbers, you should use the PadLeft() function;
int Value = 101;
char pad = '0';
String sValue = Value.ToString();
sValue = sValue.s.PadLeft(5, char)
In this case, you don't have to test whether to add 1, 2 or 3 zeros, it'll automatically add the number of zeros needed to make it 5 digits number.
int input_number = Convert.ToInt32(txtinput.Text);
string number_value = input_number.ToString("00000");
I hope that it will solve your problem. It worked well for me in my previous project.
Test this code in your development. It should be worked properly without doubt.
Same as #Jojo's answer, but using C# 6's interpolated strings:
var value = $"{number:00000}";
Apart from String.Format, You can also use String.PadLeft
value = number.ToString().PadLeft(5, '0');

Remove formatting from a string: "(123) 456-7890" => "1234567890"?

I have a string when a telephone number is inputted - there is a mask so it always looks like "(123) 456-7890" - I'd like to take the formatting out before saving it to the DB.
How can I do that?
One possibility using linq is:
string justDigits = new string(s.Where(c => char.IsDigit(c)).ToArray());
Adding the cleaner/shorter version thanks to craigmoliver
string justDigits = new string(s.Where(char.IsDigit).ToArray())
You can use a regular expression to remove all non-digit characters:
string phoneNumber = "(123) 456-7890";
phoneNumber = Regex.Replace(phoneNumber, #"[^\d]", "");
Then further on - depending on your requirements - you can either store the number as a string or as an integer. To convert the number to an integer type you will have the following options:
// throws if phoneNumber is null or cannot be parsed
long number = Int64.Parse(phoneNumber, NumberStyles.Integer, CultureInfo.InvariantCulture);
// same as Int64.Parse, but returns 0 if phoneNumber is null
number = Convert.ToInt64(phoneNumber);
// does not throw, but returns true on success
if (Int64.TryParse(phoneNumber, NumberStyles.Integer,
CultureInfo.InvariantCulture, out number))
{
// parse was successful
}
Since nobody did a for loop.
long GetPhoneNumber(string PhoneNumberText)
{
// Returns 0 on error
StringBuilder TempPhoneNumber = new StringBuilder(PhoneNumberText.Length);
for (int i=0;i<PhoneNumberText.Length;i++)
{
if (!char.IsDigit(PhoneNumberText[i]))
continue;
TempPhoneNumber.Append(PhoneNumberText[i]);
}
PhoneNumberText = TempPhoneNumber.ToString();
if (PhoneNumberText.Length == 0)
return 0;// No point trying to parse nothing
long PhoneNumber = 0;
if(!long.TryParse(PhoneNumberText,out PhoneNumber))
return 0; // Failed to parse string
return PhoneNumber;
}
used like this:
long phoneNumber = GetPhoneNumber("(123) 456-7890");
Update
As pr commented many countries do have zero's in the begining of the number, if you need to support that, then you have to return a string not a long. To change my code to do that do the following:
1) Change function return type from long to string.
2) Make the function return null instead of 0 on error
3) On successfull parse make it return PhoneNumberText
You can make it work for that number with the addition of a simple regex replacement, but I'd look out for higher initial digits. For example, (876) 543-2019 will overflow an integer variable.
string digits = Regex.Replace(formatted, #"\D", String.Empty, RegexOptions.Compiled);
Aside from all of the other correct answers, storing phone numbers as integers or otherwise stripping out formatting might be a bad idea.
Here are a couple considerations:
Users may provide international phone numbers that don't fit your expectations. See these examples So the usual groupings for standard US numbers wouldn't fit.
Users may NEED to provide an extension, eg (555) 555-5555 ext#343 The # key is actually on the dialer/phone, but can't be encoded in an integer. Users may also need to supply the * key.
Some devices allow you to insert pauses (usually with the character P), which may be necessary for extensions or menu systems, or dialing into certain phone systems (eg, overseas). These also can't be encoded as integers.
[EDIT]
It might be a good idea to store both an integer version and a string version in the database. Also, when storing strings, you could reduce all punctuation to whitespace using one of the methods noted above. A regular expression for this might be:
// (222) 222-2222 ext# 333 -> 222 222 2222 # 333
phoneString = Regex.Replace(phoneString, #"[^\d#*P]", " ");
// (222) 222-2222 ext# 333 -> 2222222222333 (information lost)
phoneNumber = Regex.Replace(phoneString, #"[^\d]", "");
// you could try to avoid losing "ext" strings as in (222) 222-2222 ext.333 thus:
phoneString = Regex.Replace(phoneString, #"ex\w+", "#");
phoneString = Regex.Replace(phoneString, #"[^\d#*P]", " ");
Try this:
string s = "(123) 456-7890";
UInt64 i = UInt64.Parse(
s.Replace("(","")
.Replace(")","")
.Replace(" ","")
.Replace("-",""));
You should be safe with this since the input is masked.
You could use a regular expression or you could loop over each character and use char.IsNumber function.
You would be better off using regular expressions. An int by definition is just a number, but you desire the formatting characters to make it a phone number, which is a string.
There are numerous posts about phone number validation, see A comprehensive regex for phone number validation for starters.
As many answers already mention, you need to strip out the non-digit characters first before trying to parse the number. You can do this using a regular expression.
Regex.Replace("(123) 456-7890", #"\D", String.Empty) // "1234567890"
However, note that the largest positive value int can hold is 2,147,483,647 so any number with an area code greater than 214 would cause an overflow. You're better off using long in this situation.
Leading zeros won't be a problem for North American numbers, as area codes cannot start with a zero or a one.
Alternative using Linq:
string phoneNumber = "(403) 259-7898";
var phoneStr = new string(phoneNumber.Where(i=> i >= 48 && i <= 57).ToArray());
This is basically a special case of C#: Removing common invalid characters from a string: improve this algorithm. Where your formatng incl. White space are treated as "bad characters"
'you can use module / inside sub main form VB.net
Public Function ClearFormat(ByVal Strinput As String) As String
Dim hasil As String
Dim Hrf As Char
For i = 0 To Strinput.Length - 1
Hrf = Strinput.Substring(i, 1)
If IsNumeric(Hrf) Then
hasil &= Hrf
End If
Next
Return Strinput
End Function
'you can call this function like this
' Phone= ClearFormat(Phone)
public static string DigitsOnly(this string phoneNumber)
{
return new string(
new[]
{
// phoneNumber[0], (
phoneNumber[1], // 6
phoneNumber[2], // 1
phoneNumber[3], // 7
// phoneNumber[4], )
// phoneNumber[5],
phoneNumber[6], // 8
phoneNumber[7], // 6
phoneNumber[8], // 7
// phoneNumber[9], -
phoneNumber[10], // 5
phoneNumber[11], // 3
phoneNumber[12], // 0
phoneNumber[13] // 9
});
}

universal parsing of strings into float

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

Categories

Resources