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 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
By default,
double.Parse
uses the currently definedCultureInfo
.Since your configure region is
United States
,en-US
is used asCultureInfo
(.
as decimal comma).The configured region of your friend is
Netherlands
, which meansdouble.Parse
(probably) usesnl-NL
asCultureInfo
and treats,
as decimal comma.To avoid this, you can tell
double.Parse
to use an independent (invariant) culture by passingCultureInfo.InvariantCulture
:CultureInfo.InvariantCulture
uses.
as a decimal separator and,
as a thousands separator.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: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: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.