skip to Main Content

We have a GPS logger app that reads a string of response from Serial port, calculates latitude and longitude and displays them on a label. I used Visual studio 2022 + WPF + .net 8.

The input data format as received by program on both computers, it has fixed-length fields:
$GNGGA,132233.80,5051.22113,N,00542.35312,E,2,12,0.57,52.6,M,46.4,M,,0136*7E

The numbers 5051.22113 and 00542.35312 are Lat/Lon values.

The part of the code that converts them to double:

double latitude, longitude;
if (gps_response_valid == true && gps_response.Contains("$GNGGA") && gps_response.Contains('M'))
{
    int ptr = gps_response.IndexOf("$GNGGA");
    string lat = gps_response.Substring(ptr + 17, 10);
    latitude = double.Parse(lat.Substring(0, 2)) + (double.Parse(lat.Substring(2, 8)) / 60);
    if (gps_response[ptr + 28] == 'S') latitude = -latitude;
    //write_to_log(latitude.ToString());
    string lon = gps_response.Substring(ptr + 30, 11);
    longitude = double.Parse(lon.Substring(0, 3)) + (double.Parse(lon.Substring(3, 8)) / 60);
    //write_to_log(longitude.ToString());
    label_lat_data.Text = latitude.ToString();
    label_lon_data.Text = longitude.ToString();
}

The exact same program on my computer gives my location as 50.853674166666664 and 5.7059495 which is correct (Now you have my real location!).

Running the program on my colleague’s computer gives 85420,86666666667 and 70584,81666666667. (note the "," instead of "." as decimal point)

  • My friend does not have Visual studio installed on his system. I just handed him the whole bin folder net8.0-windows with .exe app in it.
  • First running the app, windows asked us to install a runtime library and we did, then we could run the program.

My regional format:

My regional format

My friend’s regional format:

My friend's regional format

So what’s the problem here? We have some recorded data and we need to find out what happened to be able to correct the data.

2

Answers


  1. By default, double.Parse uses the currently defined CultureInfo.

    Since your configure region is United States, en-US is used as CultureInfo (. as decimal comma).

    The configured region of your friend is Netherlands, which means double.Parse (probably) uses nl-NL as CultureInfo and treats , as decimal comma.

    To avoid this, you can tell double.Parse to use an independent (invariant) culture by passing CultureInfo.InvariantCulture:

    double.Parse(YOUR_STRING, CultureInfo.InvariantCulture);
    

    CultureInfo.InvariantCulture uses . as a decimal separator and , as a thousands separator.

    Login or Signup to reply.
  2. Double.Parse() by default uses the culture settings from the local system where it runs. Not all cultures use a period as the decimal separator, and there can be other odd formatting quirks as well. If your app will run on computers from differing cultures, you have to think carefully about the inputs:

    • When parsing inputs provided directly by the user of the local computer, the local culture on the computer is still correct.
    • When parsing inputs provided by a user from a remote computer, you need to check where there is culture information available to you for parsing the value.
    • When parsing inputs generated by a machine with a specific culture, you need to know the culture settings of the machine.

    For example of the third option, imaging you work in America for a company based in Germany. You might need to be prepared to parse inputs using the German culture settings, even when running on your local system set in English. More commonly, international organizations will set all their server systems to generate values using a common "invariant" culture. In this way, computers will be able to read those values no matter where they are.

    You have this last situation.

    Many American (and British) developers are surprised to learn about this. They have become accustom to this default culture closely matching their own, thanks to many large tech companies growing up in California and Seattle. Almost none of us where immune, and for a long time we got away with just using the default culture from our own systems. We all have to learn about it at some point… usually the hard way; it causes a lot of bugs.

    Instead, when reading values, you should think about which situation you have, and (when appropriate) use the .Parse() overload that allows to specify the invariant (or other) culture:

    double.Parse(lon.Substring(0, 3), CultureInfo.InvariantCulture)
    

    Similarly, when outputting numeric values to strings, you should think about what kind of situation you have. Are you generating numbers for a machine? Force the invariant culture. Are you generating formatted numbers for a person, on the local system? Use the default culture on the system. Are you generating formatted numbers for a person on a remote system? Check if there’s a CultureInfo you can use or construct that will match this person’s expectation.

    The final lesson is this stuff can be complicated. Thanks to these cultural/internationalization issue, converting back and forth between numbers (or dates!) and strings is not the simple operation we might expect. It can be surprisingly complicated, and therefore slow and error-prone; something to avoid. Therefore, the best strategy is typically to parse these values into a real numeric (or date) type early, and the keep them that way as long as possible, and only convert back to string at the last possible moment before showing to the user or writing for output.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search