Skip to content

Conversation

@jmfermun
Copy link

Function minmea_tocoord now converts positions from type minmea_float to type double.

Conversion from latitude and longitude to meters:

  • delta_latitude_in_meters = delta_latitude_in_degrees * 40008000 / 360
  • delta_longitude_in_meters = delta_longitude_in_degrees * 40075160 * cos(latitude_in_degrees) / 360
  • The below examples are centered in the conversions of longitude to meters in its worst case (latitude 0º).

The use of the type minmea_float for storing the positions seems to be convenient:

  • Mapping of digits in 32 bit integer range:
    2**31 21474 83648
    Latitude XDDMM.MMMMM
    Longitude DDDMM.MMMMM
  • It allows to store 5 decimals, so we will obtain an error of 0.00001 / 60 * 40075160 * cos(0) / 360 = 0.01855 m.
  • In the readme it is specified an error of 2 cm. It matches with the above value.

Use of float for storing the positions is not convenient:

  • The precision of float is between 6 and 7 digits.
  • The precission of double is between 15 and 16 digits.
  • The worst case is with a latitude 0.0 º (equator) and a longitude with 3 digits used by the degrees part.
  • If we use the type float, 4 digits are available for the decimal part of the longitude, so we will obtain an error of 0.0001 * 40075160 * cos(0) / 360 = 11.13 m.
  • If we use the type double, 13 digits are available for the decimal part of the longitude, so we will obtain an error of 0.0000000000001 * 40075160 * cos(0) / 360 = 0.00000001113 m.
  • We need at least 7 digits for the decimal part of the longitude to obtain an error of 0.0000001 * 40075160 * cos(0) / 360 = 0.01113 m.

References:

… to type double.

Conversion from latitude and longitude to meters:
- delta_latitude_in_meters = delta_latitude_in_degrees * 40008000 / 360
- delta_longitude_in_meters = delta_longitude_in_degrees * 40075160 * cos(latitude_in_degrees) / 360
- The below examples are centered in the conversions of longitude to meters in its worst case (latitude 0º).

The use of the type minmea_float for storing the positions seems to be convenient:
- Mapping of digits in 32 bit integer range:
    2**31       21474 83648
    Latitude    XDDMM.MMMMM
    Longitude   DDDMM.MMMMM
- It allows to store 5 decimals, so we will obtain an error of 0.00001 / 60 * 40075160 * cos(0) / 360 = 0.01855 m.
- In the readme it is specified an error of 2 cm. It matches with the above value.

Use of float for storing the positions is not convenient:
- The precision of float is between 6 and 7 digits.
- The precission of double is between 15 and 16 digits.
- The worst case is with a latitude 0.0 º (equator) and a longitude with 3 digits used by the degrees part.
- If we use the type float, 4 digits are available for the decimal part of the longitude, so we will obtain an error of 0.0001 * 40075160 * cos(0) / 360 = 11.13 m.
- If we use the type double, 13 digits are available for the decimal part of the longitude, so we will obtain an error of 0.0000000000001 * 40075160 * cos(0) / 360 = 0.00000001113 m.
- We need at least 7 digits for the decimal part of the longitude to obtain an error of 0.0000001 * 40075160 * cos(0) / 360 = 0.01113 m.

References:
- https://blog.demofox.org/2017/11/21/floating-point-precision/.
- https://stackoverflow.com/questions/3024404/transform-longitude-latitude-into-meters
@jmfermun jmfermun changed the title Lost of precission due to the use of float as type for degrees Lost of precision due to the use of float as type for degrees Jan 29, 2023
@matthieu-c-tagheuer
Copy link

I also would like this modification.

@KJ7LNW
Copy link

KJ7LNW commented Feb 20, 2024

+1, looks like a simple change. If there are MCUs out there that really can't or shouldn't be using doubles, then maybe provide a #ifdef MINMEA_USE_FLOAT for users that really need 32-bit floats?

@johlim
Copy link
Contributor

johlim commented Feb 20, 2024

I also would like this modification. there is experience of experiencing problems with float-type GPS coordinate errors.

@lucid-at-dream
Copy link

The tests are not building. You should change the use of fabsf in tests.c to fabs to follow the type change

@rovo89
Copy link

rovo89 commented Feb 9, 2025

I went one step further with #83, because GPS RTK can provide higher precision than 2 cm and according modules sent eight decimal digits. My PR includes this PR's change, including adjusted tests.

@hraftery
Copy link
Contributor

hraftery commented Feb 9, 2025

Probably stating the obvious here, but it would be a pity to leave non-double users as second class citizens. Even the few MCU's that do have an FPU that supports doubles, all do so with complex compromises on what are fundamentally 32-bit systems.

I've attempted a short survey of microcontrollers (excluding "microprocessors") on their hardware support for doubles. There's definitely mistakes here - improvements welcome!

STM32

  • F0 series: no
  • C0, G0, L0, U0, WB0: no
  • F1, F2, L1: no
  • F3, F4, G4, L4, L4+: no
  • WB, WL: no
  • F7: some? (those that have the "M7F-DP" core?)
  • H7: yes (two-core high performance, with double support on the M7F core)
  • H5, L5, U5, WBA: no
  • MP1: no

Texas Instruments

  • MSP430: no
  • MSPM0C, MSPM0L, MSPM0G: no
  • AM24x, AM26x, AM27x: yes (has the same VFPv3 single/double precision FPU as the application A8 cores)
  • C2000, C28x: no

Renesas

  • RA2, RA4, RA6: no (mixture of M4, M23 and M33 cores)
  • RA8: yes (the M-series topping M85 core has a VFPv5 that supports doubles)
  • RX100: no
  • RX200, RX600, RX700: some (the top of the line in each family have the RXv3 cores, which support doubles)

So I guess the point is that only the highest-power, highest cost MCU's have hardware-emabled support for doubles, which is probably not where most people are targeting if they're running MCUs. And even then there are various caveats when it comes to loading and unloading 64-bit workloads on a 32-bit architecture.


On reflection, perhaps this is a sensible proposal:

  1. try for a best-of-both-worlds solutions that gives RTK-level precision without doubles, like a fixed point struct or something. I can't think of anything worthwhile here.
  2. proceed with this change, because RTK support is a clear improvement to the venerable minmea library, but make it an option rather than the other way around. It makes some sense to consciously choose the higher precision version, since it's not required in all cases, and comes with significant caveats for much of minmea's audience.
    • Many will share the recollection of having a double creep into your codebase somewhere and the pain of chasing down dramatic performance problems. Some will have learned the holy practice of -Wdouble-promotion and -fsingle-precision-constant, and would be disappointed to have it contravened by minmea!

@matthieu-c-tagheuer
Copy link

So I guess the point is that only the highest-power, highest cost MCU's have hardware-emabled support for doubles, which is probably not where most people are targeting if they're running MCUs.

Today there are more and more highest cost MCU. For example new rpi pico is a M33 with fpu (previous was M0+).

Why not introduce another API :

minmea_tocoord -> float
minmea_tocoord_double -> double

User can chose if they want the result as float or double.

@rovo89
Copy link

rovo89 commented Feb 10, 2025

You all have clearly more experience with pitfalls on microcontrollers. I'm happy to make adjustments to my PR, my main goal is to make this library work with GPS RTK modules on an STM32H723 (which according to the list above is suitable for this, so I didn't run into problems).

For my better understanding: Is the pure existence of functions a problem? Because I don't see any #ifdef for the float functions either, so how else would that be "optional"? Or is it enough not to call functions using float/double to avoid runtime issues, and hope that the compiler will strip unused functions to bring down the binary size?

@hraftery
Copy link
Contributor

Is the pure existence of functions a problem?

It's a good question, and I posted before I reviewed your work in that light. I went back and took a closer look, because the mere existence of a double is not a problem. It only matters for executing code.

As a random example, the standard c lib expects doubles for the %f format. nanolib is a bit different, but if you're using stdlib there's nothing stopping you casting to double for the purposes of printf - you just have to be wary that it could be an expensive operation. What you typically want to avoid is carrying around doubles and inadvertently performing floating point operations on them. It can have insidious effects.

So it needn't be an #ifdef and could be a runtime option instead. But I still think it's important than an unsuspecting user continues to get 32-bit types only. Even int_least64_t might be a trap, albeit essential to take advantage of double precision in this case. It could just be "high precision" versions of the minmea_parse_* functions that the user can choose to call, but I haven't looked at how practical that is. I wouldn't want to encourage practically duplicating the codebase!

@matthieu-c-tagheuer
Copy link

matthieu-c-tagheuer commented Feb 11, 2025

Is there a need to introduce int_least64_t ?

What is the precision provided in nmea stream ?

In my application nmea is is [d]ddmm.mmmm : 9 or 8 digits that will fit in a minmea_float with int_least32_t.

But the conversion need to be done with double instead of float to not loose precision.

        struct minmea_float f = {48463922, 10000};

        printf("float %.10f\n", minmea_tocoord(&f));
        printf("double %.10f\n", minmea_tocoord_double(&f));
        f.value = 48463923;
        f.scale = 10000;
        printf("float %.10f\n", minmea_tocoord(&f));
        printf("double %.10f\n", minmea_tocoord_double(&f));
float 48.7732048035
double 48.7732033333
float 48.7732048035
double 48.7732050000

@rovo89
Copy link

rovo89 commented Feb 11, 2025

RTK modules - at least the one I have - adds 4 decimal digits, so it would be [d]ddmm.mmmmmmmm with up to 13 digits. That results in values larger than 4,294,967,295.

@matthieu-c-tagheuer
Copy link

Ok, so we have 2 problems here :

  • the original one for no RTK gnss where we want the conversion done as double
  • the RTK where we need some minmea_double to store 13 digits values. And here build time option is needed to know if we need to parse [d]ddmm.mmmm[mmmm] with minmea_float or minmea_double.

@hraftery
Copy link
Contributor

Oh yes, for my part I overlooked the original issue sorry.

I think the original calculations might be wrong [1], but the conclusion is the same: floats could reduce resolution to some 1.7m.

Being an accessor (rather than a critical periodic function), I think an alternative minmea_tocoord_double or something might make sense. And maybe the original needs some user warnings about loss of precision. I feel like there might be some history here, but haven't looked.

Parsing higher precision sentences produced by RTK modules does indeed sound like a somewhat separate problem. I'm not sure a build time option is necessary, since the user calls the minmea_parse functions directly and could chose an alternative, but there's pros and cons.

  • [1] floats are a binary format, so don't have decimal "digits" of precision. Worse case is when the angle is ≥128°, giving a resolution of 0.00001526

@rovo89
Copy link

rovo89 commented Feb 11, 2025

And here build time option is needed to know if we need to parse [d]ddmm.mmmm[mmmm] with minmea_float or minmea_double.

Well... this affects only latitude and longitude in RMC, GGA and GLL messages. The way the library is used in the examples, you'd declare a local struct variable before calling minmea_parse_xxx(). Two minmea_double instead of minmea_float fields, each with a value and a scale, that's 2 x 2 x 4 = 16 bytes of additional stack memory required. Most likely, you'll have the variables in different scopes/branches, so 16 bytes should be the total, not per message type.

If you're not using RTK, then that's 16 stack bytes of overhead that could be avoided - but are you really working in such a constrainted environment that you would notice this? I imagine that software which interacts with a GPS module is probably more advanced and probably has other complex interfaces, so chances are good that it's not the most low-end microcontroller.

Build-time options on the other hand add a lot of complexity for tests as you need to compile for each combination and run separate tests. So that another kind of overhead to consider.

@chmorgan chmorgan self-assigned this Jul 6, 2025
@chmorgan
Copy link
Collaborator

chmorgan commented Jul 6, 2025

@jmfermun any interest in picking this up, or anyone else here? How clean is @matthieu-c-tagheuer approach?

Why not introduce another API :

minmea_tocoord -> float
minmea_tocoord_double -> double

User can chose if they want the result as float or double.

This only works if these functions are the direct user callable api and we aren't storing floats internally and losing precision.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants