Decimal.Parse and Double.Parse System.FormatException Different behavior - c#

I do not understand why I have to specify NumberStyles.Float when using decimal.Parse and not for double.Parse
I can do:
var tmp = double.Parse("1e-2");
but not:
var tmp1 = decimal.Parse("1e-2");
Because a System.FormatException (Input string was not in a correct format) is thrown
var tmp1 = decimal.Parse("1e-2", System.Globalization.NumberStyles.Float);
someone can tell me if there is a good reason behind this behavior

It's just behaving as documented. From Double.Parse:
The s parameter is interpreted using a combination of the NumberStyles.Float and NumberStyles.AllowThousands flags.
Note that NumberStyles.Float includes NumberStyles.AllowExponent.
From Decimal.Parse:
Parameter s is interpreted using the NumberStyles.Number style.
NumberStyles.Number does not include NumberStyles.AllowExponent.
I can't reproduce your bizarre stack traces which appear to show the same call failing just after it's worked:
Decimal.Parse("1e-2") always fails for me
Decimal.Parse("1e-2", NumberStyles.Float) always works for me
Double.Parse("1e-2") always works for me
Double.Parse("1e-2", NumberStyles.Float) always works for me
As for why the "default" number style differs between the two - I suspect it's because double values typically are used in scientific scenarios where exponent-based representations are common, but decimal values typically aren't.

Related

C#: Scientific notation String to Int64 conversion failing

I'm getting an exception when trying to parse a number that is in scientific notation. Looking at other posts on how to do it, and I can't tell what I'm doing any differently than those.
I've tried the following:
System.Convert.ToInt64("1.0206e+06");
System.Convert.ToInt64("1.0206E+06"); // Uppercase 'E'
These result in a FormatException: Input string was not in the correct format.
I tried these:
Int64.Parse("1.0206e+06", System.Globalization.NumberStyles.Any);
Int64.Parse("1.0206e+06", System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture);
Int64.Parse("1.0206e+06", System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture);
These all result in an OverflowException: Value was too large or too small.
Also tried with Int32.Parse and got the same exception and message:
(long)Int32.Parse(str, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture);
Using Decimal.Parse works with the same string and parameters passed to it:
(long)Decimal.Parse(str, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture);
This answer suggests using this:
Double.Parse("1.234567E-06", System.Globalization.NumberStyles.Float);
Which is similar to my last example, I just accept all number styles, and that answer used a negative exponent. In fact, I fed that exact string into my examples and I still get the same exceptions.
Not sure if it matters, but I'm using Mono C#, the version that comes with Unity.
Here's the C# source file: https://github.com/Unity-Technologies/mono/blob/unity-staging/mcs/class/corlib/System/Int64.cs. The exception is thrown on line 469 and doesn't provide me a call stack before that point. But I'm guessing the exception is created on line 355 or 372 since those match the exception type and message I'm being shown.
I'm going to assume that this is a bug with the version of Mono C# I'm using, which comes with Unity 5.5.x or earlier. Their repository can be found here.
Their implementation of Int64.Parse does not even check for the NumberStyles.AllowExponents flag, or handle exponents in any way. So it's going to fail when it finds the + symbol in the string. Basically, Int64.Parse when using Unity does not support exponents.
Mono's Int32.Parse does seem to look for exponents, but still causes an OverflowException with all exponents that I give it.
Decimal.Parse actually does work with the same parameters as the other two, which suggests there was nothing wrong with the string or parameters, but it's just a bug in their other Parse methods. Decimal's parsing is completely different from how the Int parsing is being done, so that may explain why it works and the others don't.

double.Parse not working on DataGridView Cell Value

I've got a DataGridView that I'd like to apply formatting changes to based on cell values. I'd like to do it using the original DataTable source but unfortunately building the source required removing and reorienting certain rows so the indexes are off. As such, I'm trying to do it using
double.TryParse(DataGridView
.Rows[index]
.Cells["ColumnName"]
.FormattedValue.ToString(), out dbl);
however, it never recognizes the value as a double. I've verified the output using MessageBox'es and that there are no leading or trailing spaces that might cause issue by adding "-" to both sides of the MessageBox string. I'm clueless as to why it will always regard the value as not a double even on values that are clearly parseable as doubles.
Edit : I'm not sure what changed over the weekend but I came in, fixed a different bug unrelated to this and now the bold is working using .Value rather than .FormattedValue as suggested by gh0st below.
double.TryParse() will parse the string in a culture dependant way. It is very likely that your current culture expects the decimal point to be some other symbol, like the comma or something.
For instance, on my machine, I can force it to fail the parsing by passing a different culture, like this:
double.TryParse("1.2",
NumberStyles.Float | NumberStyles.AllowThousands,
CultureInfo.GetCultureInfo("fr-FR"),
out dbl); // returns false
If this is what is happening to you, then either you find a way to change the current culture, or perhaps you can do the parsing this way instead to ensure consistent results:
double.TryParse("1.2",
NumberStyles.Float | NumberStyles.AllowThousands,
CultureInfo.InvariantCulture,
out dbl); // returns true
(*) Note: the NumberStyles.Float | NumberStyles.AllowThousands values can be adjusted as you like. I just set it to that in this example, because that is the default that the TryParse method uses when using the overload with less parameters.

Strange behaviour of String.Format when (mis-)using placeholders

When I learned about the String.Format function, I did the mistake to think that it's acceptable to name the placeholders after the colon, so I wrote code like this:
String.Format("A message: '{0:message}'", "My message");
//output: "A message: 'My message'"
I just realized that the string behind the colon is used to define the format of the placeholder and may not be used to add a comment as I did.
But apparently, the string behind the colon is used for the placeholder if:
I want to fill the placeholder with an integer and
I use an unrecognized formating-string behind the colon
But this doesn't explain to me, why the string behind the colon is used for the placeholder if I provide an integer.
Some examples:
//Works for strings
String.Format("My number is {0:number}!", "10")
//output: "My number is 10!"
//Works without formating-string
String.Format("My number is {0}!", 10)
//output: "My number is 10!"
//Works with recognized formating string
String.Format("My number is {0:d}!", 10)
//output: "My number is 10!"
//Does not work with unrecognized formating string
String.Format("My number is {0:number}!", 10)
//output: "My number is number!"
Why is there a difference between the handling of strings and integers? And why is the fallback to output the formating string instead of the given value?
Just review the MSDN page about composite formatting for clarity.
A basic synopsis, the format item syntax is:
{ index[,alignment][:formatString]}
So what appears after the : colon is the formatString. Look at the "Format String Component" section of the MSDN page for what kind of format strings are predefined. You will not see System.String mentioned in that list. Which is no great surprise, a string is already "formatted" and will only ever appear in the output as-is.
Composite formatting is pretty lenient to mistakes, it won't throw an exception when you specify an illegal format string. That the one you used isn't legal is already pretty evident from the output you get. And most of all, the scheme is extensible. You can actually make a :message format string legal, a class can implement the ICustomFormatter interface to implement its own custom formatting. Which of course isn't going to happen on System.String, you cannot modify that class.
So this works as expected. If you don't get the output you expected then this is pretty easy to debug, you've just go two mistakes to consider. The debugger eliminates one (wrong argument), your eyes eliminates the other.
String.Format article on MSDN has following description:
A format item has this syntax: { index[,alignment][ :formatString] }
...
formatString Optional.
A string that specifies the format of the
corresponding argument's result string. If you omit formatString, the
corresponding argument's parameterless ToString method is called to
produce its string representation. If you specify formatString, the
argument referenced by the format item must implement the IFormattable
interface.
If we directly format the value using the IFormattable we will have the same result:
String garbageFormatted = (10 as IFormattable).ToString("garbage in place of int",
CultureInfo.CurrentCulture.NumberFormat);
Console.WriteLine(garbageFormatted); // Writes the "garbage in place of int"
So it seems that it is something close to the "garbage in, garbage out" problem in the implementation of the IFormattable interface on Int32 type(and possibly on other types as well). The String class does not implement IFormattable, so any format specifier is left unused and .ToString(IFormatProvider) is called instead.
Also:
Ildasm shows that Int32.ToString(String, INumberFormat) internally calls
string System.Number::FormatInt32(int32,
string,
class System.Globalization.NumberFormatInfo)
But it is the internalcall method (extern implemented somewhere in native code), so Ildasm is of no use if we want to determine the source of the problem.
EDIT - CULPRIT:
After reading the How to see code of method which marked as MethodImplOptions.InternalCall? I've used the source code from Shared Source Common Language Infrastructure 2.0 Release (it is .NET 2.0 but nonetheless) in attempt to find a culprit.
Code for the Number.FormatInt32 is located in the ...\sscli20\clr\src\vm\comnumber.cpp file.
The culprit could be deduced from the default section of the format switch statement of the FCIMPL3(Object*, COMNumber::FormatInt32, INT32 value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE):
default:
NUMBER number;
Int32ToNumber(value, &number);
if (fmt != 0) {
gc.refRetString = NumberToString(&number, fmt, digits, gc.refNumFmt);
break;
}
gc.refRetString = NumberToStringFormat(&number, gc.refFormat, gc.refNumFmt);
break;
The fmt var is 0, so the NumberToStringFormat(&number, gc.refFormat, gc.refNumFmt); is being called.
It leads us to nothing else than to the second switch statement default section in the NumberToStringFormat method, that is located in the loop that enumerates every format string character. It is very simple:
default:
*dst++ = ch;
It just plain copies every character from the format string into the output array, that's how the format string ends repeated in the output.
From one point of view it allows to really use garbage format strings that will output nothing useful, but from other point of view it will allow you to use something like:
String garbageFormatted = (1234 as IFormattable).ToString("0 thousands and ### in thousand",
CultureInfo.CurrentCulture.NumberFormat);
Console.WriteLine(garbageFormatted);
// Writes the "1 thousands and 234 in thousand"
that can be handy in some situations.
Interesting behavior indeed BUT NOT unaccounted for.
Your last example works when
if String.Format("My number is {0:n}!", 10)
but revert to the observed beahvior when
if String.Format("My number is {0:nu}!", 10)`.
This prompts to search about the Standard Numeric Format Specifier article on MSDN where you can read
Standard numeric format strings are used to format common numeric
types. A standard numeric format string takes the form Axx, where:
A is a single alphabetic character called the format specifier. Any
numeric format string that contains more than one alphabetic
character, including white space, is interpreted as a custom numeric
format string. For more information, see Custom Numeric Format
Strings.
The same article explains: if you have a SINGLE letter that is not recognized you get an exception.
Indeed
if String.Format("My number is {0:K}!", 10)`.
throws the FormatException as explained.
Now looking in the Custom Numeric Format Strings chapter you will find a table of eligible letters and their possible mixings, but at the end of the table you could read
Other
All other characters
The character is copied to the result string unchanged.
So I think that you have created a format string that cannot in any way print that number because there is no valid format specifier where the number 10 should be 'formatted'.
No it's not acceptable to place anything you like after the colon. Putting anything other than a recognized format specifier is likely to result in either an exception or unpredictable behaviour as you've demonstrated. I don't think you can expect string.Format to behave consistently when you're passing it arguments which are completely inconsistent with the documented formatting types

Is Int32.ToString() culture-specific?

I'm running a beta version of ReSharper, and it's giving me warnings for the following code:
int id;
// ...
DoSomethingWith(id.ToString());
The warning is on the id.ToString() call, and it's telling me "Specify a culture in string conversion explicitly". I understand the warning, and I know how to fix it -- just change the code to the much more unwieldy id.ToString(CultureInfo.InvariantCulture).
But my question is: is that necessary? I mean, obviously it's important to specify the culture when you're using types like DateTime (different cultures have different date formats) and Double (different characters used for the decimal point). But Int32.ToString(), at least in the en-US and invariant cultures, doesn't add any formatting at all. No commas, no decimal points, no dollar signs, nothing. So what would there be to vary by culture?
Are there some cultures that actually add some sort of formatting when you call the parameterless Int32.ToString()? Or is this a bug in the ReSharper beta, and this warning really isn't applicable to Int32 (in which case I'll file a ReSharper bug report)?
The Operating System allows to change the negative sign for numbers.
Control panel ->
Language and regional settings ->
Additional settings ->
Negative sign
So, current culture could have overridden the negative sign. In this case you need to respect the regional settings, this is the reason of the warning. You can also change the negative sign programatically:
CultureInfo culture = Thread.CurrentThread.CurrentCulture;
// Make a writable clone
culture = (CultureInfo) culture.Clone();
culture.NumberFormat.NegativeSign = "!";
As tested on a random sample of ints, all 352 cultures installed with Windows (CultureTypes.InstalledWin32Cultures) give identical results.
Daniel is right to note a custom culture could use a different prefix for negative numbers, but I doubt anyone has ever used this feature save by accident.
I guess the .NET devs did it to be consistent with float and other types. What else were they expecting?
> int.MaxValue.ToString(CultureInfo.AncientRome)
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM....
It's weird; I would have expected 50.ToString(CultureInfo.CreateSpecificCulture("ar-AE")) to return "٥٠", but it doesn't.
I've just looked this up, and the problem seems to be that NumberFormatInfo.DigitSubstitution isn't actually implemented
The DigitSubstitution property is reserved for future use. Currently, it is not used in either parsing or formatting operations for the current NumberFormatInfo object.
So, although there is an enumeration System.Globalization.DigitShapes, it's not actually implemented in the NumberFormatInfo bit of IFormatProvider.
Yes. It depends on the current culture. From the MSDN docs:
The return value is formatted with the general numeric format specifier ("G") and the NumberFormatInfo object for the current culture.
emphasis mine
Resharper just most likely wants you to be explicit about what culture you are intending to use. Since omitting it relies on behavior that may change when executed on different machines.
The compiler is (unnecessarily) warning us about it COULD be casted to string somehow not as we expected it to be. For example:
int i = 1;
Console.WriteLine("i.ToString='{0}'", i.ToString());
We all expect it to return as '1' but it is not 100% guaranteed because .ToString() method gets affected by the current thread culture. It CLAIMS that it could return as '1.00' or something like that but I tested it with a really small code:
foreach(CultureInfo ci In System.Globalization.CultureInfo.GetCultures(CultureTypes.AllCultures) {
Console.WriteLine("RESULT='{0}' CultureCode: {1} EnglishName:{2}", i.ToString(ci), ci.Name, ci.EnglishName);
if (!i.ToString(ci).Equals("1"))
throw new FormatException()
}
The code never returned any error. So, it actually never returns anything other than "1".
As far as there will be a new country with a brand new native super-weirdo language in this world that actually spells "1.00" for an integer, we can keep using just .ToString() without any doubt.
Cheers!
Output was:
I would have said no, but on checking MSDN Int32.ToString() there's this:
The return value is formatted with the general numeric format specifier ("G") and the NumberFormatInfo object for the current culture.
So there's a surprise.
The question should be why doesn't the current Resharper suggest this?
Because integers can be be as high as 2,147,483,647.
In some countries, they would use decimals or a space in place of the commas.

Best way to parse float?

What is the best way to parse a float in CSharp?
I know about TryParse, but what I'm particularly wondering about is dots, commas etc.
I'm having problems with my website. On my dev server, the ',' is for decimals, the '.' for separator. On the prod server though, it is the other way round.
How can I best capture this?
I agree with leppie's reply; to put that in terms of code:
string s = "123,456.789";
float f = float.Parse(s, CultureInfo.InvariantCulture);
Depends where the input is coming from.
If your input comes from the user, you should use the CultureInfo the user/page is using (Thread.CurrentThread.CurrentUICulture).
You can get and indication of the culture of the user, by looking at the HttpRequest.UserLanguages property. (Not correct 100%, but I've found it a very good first guess) With that information, you can set the Thread.CurrentThread.CurrentUICulture at the start of the page.
If your input comes from an internal source, you can use the InvariantCulture to parse the string.
The Parse method is somewhat easier to use, if your input is from a controlled source. That is, you have already validated the string. Parse throws a (slow) exception if its fails.
If the input is uncontrolled, (from the user, or other Internet source) the TryParse looks better to me.
If you want persist values ( numbers, date, time, etc... ) for internal purpose. Everytime use "InvariantCulture" for formating & parsing values. "InvariantCulture" is same on every computer, every OS with any user's culture/language/etc...
string strFloat = (15.789f).ToString(System.Globalization.CultureInfo.InvariantInfo);
float numFloat = float.Parse(System.Globalization.CultureInfo.InvariantInfo, strFloat);
string strNow = DateTime.Now.ToString(System.Globalization.CultureInfo.InvariantInfo);
DateTime now = DateTime.Parse(System.Globalization.CultureInfo.InvariantInfo, strNow);
You could always use the overload of Parse which includes the culture to use?
For instance:
double number = Double.Parse("42,22", new CultureInfo("nl-NL").NumberFormat); // dutch number formatting
If you have control over all your data, you should use "CultureInfo.InvariantCulture" in all of your code.
Use a neutral culture (or one you know) when parsing with Try/Parse.
Pass in a CultureInfo or NumberFormatInfo that represents the culture you want to parse the float as; this controls what characters are used for decimals, group separators, etc.
For example to ensure that the '.' character was treated as the decimal indicator you could pass in CultureInfo.InvariantCulture (this one is typically very useful in server applications where you tend to want things to be the same irrespective of the environment's culture).
Try to avoid float.Parse, use TryParse instead as it performs a lot better but does the same job.
this also applies to double, DateTime, etc...
(some types also offer TryParseExact which also performs even better!)
The source is an input from a website. I can't rely on it being valid. So I went with TryParse as mentioned before.
But I can't figure out how to give the currentCulture to it.
Also, this would give me the culture of the server it's currently running on, but since it's the world wide web, the user can be from anywhere...
you can know current Cuklture of your server with a simple statement:
System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.CurrentCulture;
Note that there id a CurrentUICulture property, but UICulture is used from ResourceMeanager form multilanguages applications. for number formatting, you must considere CurrentCulture.
I hope this will help you
One approach is to force localization to use dot instead of comma separator - this way your code will work identically on all windows machines independently from selected language and settings.
This approach is applicable to small gained applications, like test applications, console applications and so on. For application, which was localization in use this is not so useful, but depends on requirements of application.
var CurrentCultureInfo = new CultureInfo("en", false);
CurrentCultureInfo.NumberFormat.NumberDecimalSeparator = ".";
CurrentCultureInfo.NumberFormat.CurrencyDecimalSeparator = ".";
Thread.CurrentThread.CurrentUICulture = CurrentCultureInfo;
Thread.CurrentThread.CurrentCulture = CurrentCultureInfo;
CultureInfo.DefaultThreadCurrentCulture = CurrentCultureInfo;
This code forces to use dot ('.') instead of comma, needs to be placed at application startup.
Since you don't know the web user's culture, you can do some guesswork. TryParse with a culture that uses , for separators and . for decimal, AND TryParse with a culture that uses . for separators and , for decimal. If they both succeed but yield different answers then you'll have to ask the user which they intended. Otherwise you can proceed normally, given your two equal results or one usable result or no usable result.

Categories