C# Number format dealing with digit grouping - c#

I encountered an issue with C# (and Java) on the parsing/validation of culture-sensitive numerical formatting. It seems like when it comes to digit grouping, the separator can be placed anywhere in .NET. Is there a way to enforce a strict adherence of the usage of the digit grouping? For instance, see the following:
Decimal.Parse("9,0"); /// Returns 90, which is wrong
Decimal.Parse("90,00"); /// Returns 9000, which is wrong
Decimal.Parse("9,000"); /// Returns 9000, which is right
To complicate things, cultures differ in the number of digits per group.
Any suggestions?
Edit: It was suggested I add CultureInfo into the Parse(), but that does not work properly still. For instance:
CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US"); /// Murican English
Double.Parse("9,0", culture); /// Returns 90 when it should throw an exception
culture = CultureInfo.CreateSpecificCulture("pt-BR"); /// Brazillian Portuguese
Double.Parse("9.0", culture); /// Returns 90 when it should throw an exception

you can find information on parsing in here as use CultureInfo culture as seen in the example in the link
for example
culture = CultureInfo.CreateSpecificCulture("en-US");
number = Double.Parse(value, culture);// 1,304.16 --> 1304.16
but "en-US" can't parse "1 304,16". "fr-FR" can --> you'll get 1304.16

You should specify CultureInfo, since parsing results are culture dependent e.g.
// English, United States:
// "," is a thousand but not decimal separator, decimal separator is "."
// d1 = 90 since "," is NOT a decimal separator
Decimal d1 = Decimal.Parse("9,0", new CultureInfo("en-US")); // <- 90
// Russian, Russia:
// "," is a decimal separator
// d2 = 9.0 since "," is a decimal separator
Decimal d2 = Decimal.Parse("9,0", new CultureInfo("ru-RU")); // <- 9.0

For correctly parsing the numbers you definetly need the Source Culture Info of the number.
Refer this
Parsing numbers from different cultures in C#

Related

How to prevent decimal.TryParse convert "," to "."?

This is my code:
string myValue = "0,203";
decimal.TryParse(myValue, NumberStyles.Any, CultureInfo.CurrentCulture, out myValueAsDecimal;
...
myValueAsDecimal is 0.203 now
Is it possible that myValueAsDecimal has 0,203 after TryParse or the internal representation of decimal is always 0.203 and I need to format GUI output if I need 0,203?
Is it possible that myValueAsDecimal has 0,203 after TryParse
No. It's just a number - it has no concept of a text format. To think about it another way with a simpler type, consider these two lines of code:
int x = 0x100;
int y = 256;
Are those two values the same? Yes, they represent the same number. If you convert the values of x and y to strings, by default they will both end up as "256" - but they could both end up as "100" if you request a hex representation.
It's important to distinguish between the real value of a variable and a textual representation. Very few types (none that I can think of immediately) carry around information about a textual representation with them - so for example, a DateTime can be parsed from a variety of formats, but has no "memory" of an original text format. It's just a date and time, which could then be formatted according to any format.
If you need to maintain the idea of "a decimal number and the culture in which it was originally represented" then you should create your own class or struct for that pairing. It's not present in decimal itself.
decimal d = 0.203m;
Console.WriteLine(d.ToString(CultureInfo.InstalledUICulture));
Console.WriteLine(d.ToString(CultureInfo.InvariantCulture)); // decimal point: dot
Console.WriteLine(d.ToString(CultureInfo.GetCultureInfo("en-US"))); // default decimal point: dot
Console.WriteLine(d.ToString(CultureInfo.GetCultureInfo("ru-RU"))); // default decimal point: comma
Result:
0,203
0.203
0.203
0,203
Looks like your CurrentCulture has , as a NumberDecimalSeparator and that's why your parsing succeed.
Actually, 0.203 and 0,203 are the same as value. Only matter is their textual representation when you print it.
If you wanna get your value as a 0,203 representation, you can use a culture that has , as a NumberDecimalSeparator.
For example, my culture (tr-TR) has a ,. When you represent your decimal with it, you will get 0,203.
string myValue = "0,203";
decimal myValueAsDecimal;
decimal.TryParse(myValue, NumberStyles.Any, CultureInfo.CurrentCulture, out myValueAsDecimal);
myValueAsDecimal.ToString(new CultureInfo("tr-TR")).Dump(); // 0,203
The value of the Decimal is the same regardless of the Culture, it's
0.203
what changes is its String representation (decimal separator in your case), so
if you want to change decimal separator and don't want to change the Culture
you can just assign NumberDecimalSeparator in your custom NumberFormatInfo e.g.
Decimal d = 0.203M;
NumberFormatInfo myNumberInfo = new NumberFormatInfo() {
NumberDecimalSeparator = "," // Comma, please
};
String result = d.ToString(myNumberInfo); // "0,203"

Trying to set the decimal separator for the current language, getting "Instance is read Only"

I have code that was originally written for an English language market where the decimal separator is "." so it's expecting numeric values as strings to use "." as the separator. But we now have users in other places, e.g., places in Europe where the decimal separator is ",".
So, in the context of my software (really just the current thread) I want to override the decimal separator for the current language to be "." even if it defaults to something else.
I tried
String sep = ".";
NumberFormatInfo nfi1 = NumberFormatInfo.CurrentInfo;
nfi1.NumberDecimalSeparator = sep;
But I get an "Instance is read-only" exception on the third line. Apparently NumberFormatInfo is not writable. So how DO you set the current language's decimal separator to something other than its default?
You need to create a new culture and you can use the current culture as a template and only change the separator.
Then you must set the current culture to your newly created one as you cannot change the property within current culture directly.
string CultureName = Thread.CurrentThread.CurrentCulture.Name;
CultureInfo ci = new CultureInfo(CultureName);
if (ci.NumberFormat.NumberDecimalSeparator != ".")
{
// Forcing use of decimal separator for numerical values
ci.NumberFormat.NumberDecimalSeparator = ".";
Thread.CurrentThread.CurrentCulture = ci;
}
You can use the Clone() method on the NumberFormatInfo instance, which will create a mutable version (i.e. IsReadOnly = false). You are then able set the currency symbol and/or other number format options:
string sep = ".";
NumberFormatInfo nfi1 = (NumberFormatInfo)NumberFormatInfo.CurrentInfo.Clone();
nfi1.NumberDecimalSeparator = sep;

Double.TryParse converts 0.1 to 1.0

Situation - The thread culture in my web app has been set to 'es' (Spanish)
Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture("es");
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("es");
The string value is "0.1"
For the following expression,
var value = "0.1"
provider = CultureInfo.CreateSpecificCulture("en-US")
double.TryParse(value.ToString(), NumberStyles.Any, provider, out number)
number returns 1.0. Which makes me think that it is picking the culture info from the thread. Not the one I provide.
The following unit test passes (as expected).
var numberInEnUS = "0.1";
var spanishCulture = CultureInfo.CreateSpecificCulture("es");
culture = new CultureInfo("en-US", false);
Thread.CurrentThread.CurrentCulture = spanishCulture;
Thread.CurrentThread.CurrentUICulture = spanishCulture;
double number;
double.TryParse(numberInEnUs, NumberStyles.Any, culture, out number);
Assert.AreEqual(0.1, number);
So, the question is why does double.TryParse fail in my application? Theoretically, 0.1 for Spanish is 1 (Separator for spanish is a decimal point '.'). However, number 1000.0 does not get converted to 10000. So, it seems that it fails only for 0.1
Any explanation is highly appreciated!
You say "0.1" is number in spanish. Actually not, It is numberInEnglish or something else
var numberInSpanish = "0.1";//this is number in english culture
It should be
var numberInSpanish = "0,1";//<--Note 0,1
NumberDecimalSeparator for spanish is ,. Parse 0,1 you'll get expected result.
var numberInSpanish = "0,1";
var spanishCulture = CultureInfo.CreateSpecificCulture("es");
var culture = new CultureInfo("en-US", false);
Thread.CurrentThread.CurrentCulture = spanishCulture;
Thread.CurrentThread.CurrentUICulture = spanishCulture;
double number;
double.TryParse(numberInSpanish, NumberStyles.Any, spanishCulture, out number);
Here number is correctly parsed to "0.1"
Your problem is in the mixture of decimal and thousand separators, namely:
'.' - thousand separator in "es" culture, when parsing, will be ignored (e.g. 1.000,0 == 1000,0)
',' - decimal deparator in "es" culture, separates integer and fractional parts
You can easily convince yourself:
var spanishCulture = CultureInfo.CreateSpecificCulture("es");
Char dS = spanishCulture.NumberFormat.NumberDecimalSeparator; // <- ','
Char tS = spanishCulture.NumberFormat.NumberGroupSeparator; // <- '.'
So, in your case the string "0.1" will be converted into 1.0 double since '.' as
being a thousand separator in es culture will be ignored.
You can do either:
Use Invariant culture instead of "es" one:
double.TryParse(numberInNeutral, NumberStyles.Any, CultureInfo.InvariantCulture, out number);
Or use actual Spanish number representation:
var numberInSpanish = "0,1";
double.TryParse(numberInSpanish, NumberStyles.Any, culture, out number);
I finally was able to identify what was wrong. The issue was not with the TryParse() function but the ToString() function.
The value was actually a Double type, not a string as I mentioned above. (My bad, I thought it was not relevant). I was actually doing a value.ToString(). This is where it uses the thread culture and changes the value.
So, if the value was 0.1, the value.ToString() changes it to "0,1". It automatically changes the decimal character based on the Thread culture. The TryParse then uses the en-US culture and convert "0,1" to 1.
To fix it, use Convert.ToString instead and pass in the culture info.
At the end, it was just a silly mistake.
LessonLearnt - Be careful when using ToString() in globalized applications!

date format in .net - how do I say use the given cultureinfo?

WHen using the CultureInfo to render a number, #,000.00 does not literally mean place a , for the thousands separator and a . for the decimal. It means use the correct character from that culture at those locations.
However, using "mm-dd-yyyy" places the month, day, year in that order regardless of culture. Is there a way to tell it I'm passing the US format but change to match the passed in culture?
More details:
CultureInfo ci = new System.Globalization.CultureInfo("de-DE");
DateTime birthday = new DateTime(1955, 9, 26);
string displayValue = BIRTHDAY.tOsTRING("M/d/yyyy", ci);
Use DateTimeFormatInfo.ShortDatePattern instead of hard coded string.
var text = date1.ToString(CultureInfo.CreateSpecificCulture("fr-FR")
.DateTimeFormat.ShortDatePattern);

convert this string into decimal

Sounds easy but when I tried to achieve i'm stock about how is the formatter to make this conversion this are some examples of strings that i need to convert to decimal
00.24
48.34
01.24
Does anybody know how can i Accomplish this?? I tried like this
try
{
decimal x = Convert.ToDecimal("00.24", );
//Which formatter do I need to pass??
decimal x = Convert.ToDecimal("00.24", Formatter???);
}
Catch(Exception e)
{
throw new Exception()
}
But It doesn't work because the result it's 24D and i need 0.24D
I suspect your system culture is not English and has different number formatting rules. Try passing the invariant culture as the format provider:
decimal d = Convert.ToDecimal("00.24", CultureInfo.InvariantCulture);
You could also use Decimal.Parse:
decimal d = Decimal.Parse("00.24", CultureInfo.InvariantCulture);
Why not just use Decimal.Parse
decimal x = Decimal.Parse("00.24");
Console.WriteLine(x); // Prints: 00.24
I think Decimal.TryParse should work. More info here.
The result you're getting is because the dot . is tretaed as a group (thousand) separator. the parser simply discards it, and doesn't check if the group sizes are right. So '20.100.200' or '1.2.3.4' would also get parsed as 20100200 and 1234.
This happens on many european cultures, like 'es'
You have to use any culture that doesn't consider a . as a group separator, but as a decimal separator. CultureInfo.InvariantCulture is one of the possible cultures (it has basically the same configuration of en-US).

Categories