开发者

How to fix an application that has a problem with decimal separator

开发者 https://www.devze.com 2023-03-14 16:20 出处:网络
This post is about C# and .Net, but some info is valuable for other techonolgies. Since I can remember, I\'ve been having problems with apps or games that crash because of a different style of parsin

This post is about C# and .Net, but some info is valuable for other techonolgies.

Since I can remember, I've been having problems with apps or games that crash because of a different style of parsing decimal numbers. It happens very often, from CAD apps, libraries to web pages. I'm not sure whether it is ignorance or lack of knowledge, but it's really annoying.

What's the problem? Here is a wiki article about it, but it short:

Here is a map that shows what kind of decimal separator (decimal mark) is used around the world.

How to fix an application that has a problem with decimal separator

Decimal marks:

  • Period — Blue
  • Comma — Green
  • Non-West-Arabic Numerals — Red
  • Unknown — Grey

Most of the Europe, South America write 1 000 000,00 or 1000000,00 sometimes 1.000.000,00 as opposite to "imperial" (marked as blue) write 1,000,000.00

Let me just give you a few from all problems that I encoutered last month.

  1. Number on webpages hard to read: Let's take one of the most viewed YT videos. It shows me 390159851. Number above one million are very hard to read, what's the order of magnitude? Is it 39 mln or 390?
  2. Mirosoft XNA for Windows Phone 7 example: There is a very neat class that parse XML file to produce XNA animation

    /// <summary>
    /// Loads animation setting from xml file.
    /// </summary>
    private void LoadAnimiationFromXML()
    {
        XDocu开发者_开发百科ment doc = 
                  XDocument.Load("Content/Textures/AnimationsDefinition.xml");
        XName name = XName.Get("Definition");
        var definitions = doc.Document.Descendants(name);
    
    
            if (animationDefinition.Attribute("Speed") != null)
            {
                animation.SetFrameInvterval(TimeSpan.FromMilliseconds(
                    double.Parse(animationDefinition.Attribute("Speed").Value)));
            }
    

    double.Parse throws an exception, one simple soultion is to use XmlConvert.ToDouble(); or parse with InvariantCulture.

  3. .Net app that use CSV files to store input vector as CSV - also throws.

  4. Another .Net app that has some parsing inside the class - throws.

So how can we fix this?

  • Fix the bug in code - possible only with avaible source code and tedious.
  • Change the data (CVS, XML etc.) - even with possible, not very happy with mutating the data.
  • Change the OS default decimal separator - not gonna happen.

Is there any other way to slove this? Can I make the app be invariant? Like starting the app in a different environment.

PS. I would like to run the app, but I don't have the code.


Properly set Thread.CurrentThread.CurrentCulture and you wont have such problems. Read here how to write culture independent code.

EDIT: If you do not have access to the code, you can run the application under a user account which has the expected culture set. For quick access you could create an english user, a german user, a french user..


In my opinion, the approach should be following:

  1. in the internal storage, the numbers are just numbers. so no problem occurs.
  2. when parsing input text sources, you should parse them according to the locale the source is created in -- most probably, invariant culture
  3. when outputting the values to the user, format them according to the user-preferred locale. in C#, this means maintainging CurrentCulture (not CurrentUICulture, see here why).

This should be relatively simple and cover all the cases.

This all applies to the case when you are the application's author. For others' applications, the respective developers must fix their bugs themselves.


I feel your pain, the port of RunKeeper to Windows Phone 7 got this wrong, and reported me breaking the speed of light, by a factor of 100 000 last month.

If you are being sloppy you'll run into these problems but for the most part they can be avoided. Strictly speaking you should always present information in it's localized format and then convert it to a culture agnostic or base value when you save it. This way you can always target specific cultures.

The problem I believe many .NET developers run into is that they do not understand that all the default parse methods fallback on the Thread.CurrentThread.CurrentCulture and use a specific number format for that culture and it is the same with dates and time zones.

Something that I might consider doing is to setup a chain of responsibility, one in which I had the culture specific variants queried first and a culture invariant fallback. I'd never mix these but I'd support a culture agnostic default that should work if you adhered to some typical convention (e.g. English number formats). But that might not be the right choice every time, educate you user if they think parsing a mixed numerical format CSV file is a sound thing to do.


There isn't a silver bullet here.

Some suggestions:

The invariant culture can be used when dealing with internal data that is stored in a config file. This way no matter what culture the machine is you can read and write your own internal data in a consistent manner.

See here:

http://msdn.microsoft.com/en-us/library/4c5zdc6a.aspx

Other problems deal with something simple as string.ToLower().

Seems innocent enough, until you try to lowercase an English letter than doesn't exist in the current culture. (E.G. Turkish)

In these cases, calling string.ToLower() should be replaced with the culture overload and the current culture or invariant culture should be passed (depending on your situation).

Definately some pitfalls to watch out for.


There is really no other way to resolve this issue, just fix it in the code.
If you care to run FxCop you would soon learn to always pass valid CultureInfo to every possible ToString() or Parse() method. Frankly, this is the way it should be done, even when you know that CultureInfo.CurrentCulture is used by default.
By always passing IFormatProvider, you are telling other developers:

  1. ToString(CultureInfo.CurrentCulture) or Parse(whatever, CultureInfo.CurrentCulture) - this is something end user will see or will be able to enter, so we need to care about his/her cultural background.
  2. ToString(CultureInfo.InvariantCulture) or Parse(whatever, CultureInfo.InvariantCulture) - this is something we use internally and it is not meant to be shown to user.

Now, I know it sounds like a lot of work but this is simply something that needs to be done. You might just want to thank some poor soul in Microsoft, who with his/her good intention decided that end user's CurrentCulture is the best default for formatting providers... Obviously, other framework designers who did this in the past were wrong and folks from MS are right, ha? A lot of cash has evaporated because of this stupid, unfixable mistake.

0

精彩评论

暂无评论...
验证码 换一张
取 消