views:

1134

answers:

4

Hi all,

I'm having a bit of a curious problem with .NET and working with the decimal type. My web application uses the following globalization settings:

<globalization culture="auto" enableClientBasedCulture="true"/>

With that in mind, consider the following: the decimal value 123.00 becomes £123.00 in GBP, and EUR 123,00 (note the comma). The problem I'm having appears when I want to go from a decimal value of 123,00 (EUR) back to GBP again, as it becomes 123,000 - which is a huge problem.

In this scenario, my application has consults a look up table based on the detected culture and shows prices in that currency. The problem only arises when the user has made a selection and the data is prepared for sending to our payment gateway, which convers the EUR 123,00 to 123,000.

Any ideas how I can overcome this?

Thanks

+1  A: 

Make this could help you

        NumberFormatInfo ni = new NumberFormatInfo();
        ni.NumberDecimalDigits = '.';

usage i.e.

        Double.Parse(MyString, ni)

-rAyt

Don't know any better way to detect the format, but take a look at this

    static void Main(string[] args)
    {
        // German Format (EUR)
        CultureInfo ci_de = new CultureInfo("de-DE");

        // English Format (GBP)
        CultureInfo ci_gb = new CultureInfo("en-GB");


        string test_de = "1.234.567.890,12";
        string test_en = "1,234,567,890.12";

        double result_de = 0.0;
        double result_en = 0.0;

        try
        {
            result_de = Double.Parse(test_en, ci_de.NumberFormat);
        }
        catch (FormatException ex)
        {
            Console.WriteLine("Number isn't a german format " + ex.InnerException);
        }
        try
        {
            result_en = Double.Parse(test_en, ci_gb.NumberFormat);
        }
        catch (FormatException ex)
        {
            Console.WriteLine("Number isn't a english format " + ex.InnerException);
        }

        Console.WriteLine(result_de.ToString());
        Console.WriteLine(result_en.ToString());

        Console.ReadLine();
    }
Henrik P. Hessel
Hi, thanks for your reply. I tried ni.NumberDecimalDigits = "." (vb.net) but that produces a format exception. Do you mean ni.CurrencyDecimalSeparator = "."?
Richard
Please include the whole code. That helps a lot. And yes, you can use CurrencyDecimalSeparator, too. But you need to set it to CurrencyDecimalSeparator = ',' if you convert from Euro (100,00) to GBP "100.00"
Henrik P. Hessel
Ok, so that works fine if I explicitly *know* I'm converting from EUR to GBP, but that's not *always* the case. So how can I detect (purely from a decimal value) what settings to use? Thanks
Richard
added some code
Henrik P. Hessel
+1  A: 

The decimal data type basically just holds a number--there is no formatting implicitly applied to it. The value of a number won't change just because it's being displayed in a different format.

Likely, the problem that you're likely having is when you receive the input as a string from a web page, you are parsing it incorrectly.

Jim
Hi Jim, I'm thinking you're absolutely right... I confirmed in the debugger, regardless of locale, the value of the decimal var is always expressed as '123.00', instead of '123,00'. My problem then, must be occurring elsewhere along the line - as you say - when converting to a string.
Richard
A: 

rAyt has offered a good start, but if you are working with a variety of cultures, it could become very difficult to automatically detect which culture a decimal number is. To ensure that you always know, it might be best to create a simple wrapper type around decimal that includes the culture. You would need to update your data store to also store the culture code (i.e. en-US) so that when reconstituting data you don't "forget" what culture the data was saved as. Any entities you have would use your decimal wrapper type, rather than decimal directly, and that type could have an overridden ToString method to automatically render in the correct format:

public struct CulturedDecimal
{
    public decimal Value;
    public CultureInfo Culture;

    public override ToString()
    {
        return Value.ToString(Culture);
    }
}

public class SomeEntity
{
    public int ID { get; set; }
    public CulturedDecimal MonetaryValue { get; set; } // Was 'decimal' before
}
jrista
A: 

Thanks to everyone who offered their experience and suggestions.

Implementing a custom-type/structure for holding my own Decimal values wasn't really what I was after, and wouldn't really give me the functionality I needed (I can already display decimal values in the appropriate local currency/format, I just couldn't convert them back into UK format when required).

The problem was this line in the web config:

<globalization culture="auto" enableClientBasedCulture="true"/>

Setting culture = "auto" was allowing .NET to set the locale according to the values provided by the browser (incidentally, by the way, 'enableClientBasedCulture' is not implemented, according to MSDN - so you can omit it). Hence, if a visitor from France (with language 'fr-FR' configured in their browser) visited our site, all the number formatting would work perfectly (correct decimal separator and currency symbol) but I'd have a problem later when trying to conver that number from it's European format to the UK/US format I required.

It's odd, but converting "123.00" to the 'fr-FR' locale produces a FormatException because "123.00" is not valid in the French locale (it expects "123,00"). But, converting "123,00" (fr-FR) to the UK/US 'en-GB' or 'en-US' format does NOT produce an error, but instead the value becomes "123,000". I believe this should throw a FormatException because it is not acceptable to add another zero.

The solution I implemented was as follows:

  1. Set culture="auto" to culture="en-GB" in web.config.
  2. Use Decimal.ToString("c", ni) - where 'ni' is custom NumberFormatInfo class.

Since my existing code connects to our data source to retrieve the correct decimal values dependant on country, all I had now was a formatting issue. So, to format a number according to the 'fr-FR' locale, you can do:

NumberFormatInfo ni = Globalization.CultureInfo.GetCultureInfo("fr-FR").NumberFormat;
Decimal.ToString("c", ni);

In this setup, all my decimal values (internally) are always treated as en-GB decimals, and thus in the format I require. In other words, my application did not require the flexibility of being able to change the settings that apply to the entire current thread; rather just the opposite: I only cared about formatting the values differently.

Richard
You need to understand still that Decimal numbers (as in, variables you work with in your program, have nothing to do with the culture your .Net app uses, it's just a number). It is when you start trying to turn a string into a decimal, or a decimal into a string (ie. when you print it out) that culture steps in and makes a difference.That means, if you wish for .Net to handle the culture of the output you can simply do as you did before - but when reading numbers in from an external system, as a string, you will need to know which format it is entering your system in, and treat it as such.
kastermester
Also, if I am not mistaken - the fr-FR locale uses spaces for decimal point and commas for thousand separator, which is why 123,00 evaluates into 123,000. Locales are a tricky thing to deal with, and trust me, I know your pain.I deal with a system too with multiple locales and I have tackled with it in the following way. 1. All user input on the site is accepted in the incomming locale (let's say fr-FR) - and printed out in the same locale (fr-FR). When accepting input from elsewhere, it is accepted in the InvariantCulture format.
kastermester
Hi Kastermester,Thanks for your comments - your feedback is much appreciated. I do indeed understdand that the Decimal type is not 'culture-specific', which was why I was initially puzzled by the problem I had in front of me. As it is not possible (in our implementation) to accept input in any culture (and to store that culture and format it correctly later), all I really needed to do was ensure that a particular .ToString() representation of a decimal was always 'en-GB', but still respect and display decimal values in other areas of the site in the user's locale.
Richard
Now that this is resolved, I will do a little more experimentation to learn more about this subject in general. I am curious to know why in my initial testing (in pseudo-code) "123.00D".ToString("c", [fr-FR]) produces a FormatException, while "123,00D".ToString("c", [en-GB]), does not - it simply gets changed to "1230", which is of course, wrong.
Richard