Overcoming Lua floating point math precision errors (latitude/longitude)

I was writing a script to string multiple waypoint missions together by loading them sequentially from the SD card when I noticed a peculiarity in the example script method for loading waypoints.

The example scripts read the text files line by line, using a table (called data) to store each set of waypoint parameters. The latitude/longitude integer values are set like this (where data fields 9 and 10 are in floating point decimal degrees):

item:x(data[9]*10^7)
item:y(data[10]*10^7)

Unfortunately, thatā€™s only good to about 5 decimal places (~1m accuracy), and waypoints for my use case require near centimeter accuracy - the full 7 decimal places.

Fortunately, it appears that you can overcome the issue by simply manipulating the string values, like this:

-- the AP Lua implementation appears very susceptible to floating point math errors
-- this function takes a decimal number in string format and returns an integer
-- representation of its value * 10^7 (as in ArduPilot lat/lng arguments)
-- without using any floating point math (takes *much* longer)
function strf_to_stri(x)
  local whole, fraction = x:match("(.+)%.(.+)")
  if (fraction == nil) then
    whole = x
    fraction = '00000000'
  end
  if (string.len(fraction) > 8) then
    fraction = fraction:sub(1, 6 - string.len(fraction))
  end
  fraction = fraction .. string.rep('0', 8 - string.len(fraction))
  return whole .. fraction:sub(1, -2)
end

If youā€™re about to say something like, ā€œBut Yuri, just use string.format()!,ā€ I tried that. Same precision error. :smiley:

I should probably lop off the 8th fractional character a little sooner and improve the overall efficiency of the function. I was accounting for the fact that the typical input to the function is expected to have 8 decimal places (as in Mission Plannerā€™s saved waypoint files).

3 Likes

Thanks for this tip Yuri. Iā€™m not sure exactly where to insert the function into the code that is reading the waypoint table. If I try to convert the data read for index 9 or 10 (GPS coordinates) I get an error ā€œattempt to index a number value (local ā€˜xā€™)ā€.

Give me a code snippet to work with. Canā€™t help with that vague description.

local data = {}
for i = 1, 12 do
new_read = file:read(ā€˜nā€™)
if i == 9 or i == 10 then
data[i] = strf_to_stri(new_read)
else
data[i] = new_read
end

  if data[i] == nil then
    if i == 1 then
      gcs:send_text(6, 'loaded mission: ' .. file_name)
      return -- got to the end of the file
    else
      mission:clear() -- clear part loaded mission
      error('failed to read file')
    end
  end
end

You have a type conversion issue.

An easy fix is:
data[i] = strf_to_stri(tostring(new_read))

ah ā€¦ thanks so much!

regarding this process of loading missions from scripts, it took me awhile to realize the process was working (actually loading wpā€™s onto the ardupilot) because Mission Planner was not showing any of the waypoints. Is there a way to refresh the map on Mission Planner to show the loaded waypoints?

Thereā€™s a button to read the mission from the autopilot.

Too many people confuse ā€œMission Plannerā€ for their autopilot and its internal features. It is just the GCS. It only knows what comes down from telemetry.

Thanks again.
Still not sure if Iā€™m getting the right results from this code.
For example, my last waypoint is this:
|2|0|3|16|0.00000000|0.00000000|0.00000000|0.00000000|28.60903828|-81.26689837|0.000000|1|
my code says:
if i == 9 or i == 10 then
data[i] = strf_to_stri(tostring(file:read(ā€˜nā€™)))
else
data[i] = file:read(ā€˜nā€™)
end

28.60903828 produces your fraction value of 60904 and a final output of 286090400
-81.26689837 produces your fraction value of 2669 and a final output of -812669000

it seems like file:read(ā€˜nā€™) is rounding the waypoint value before it gets to your function

Ah yes! Reading as a number is problematic. You need to read those tokens as strings.

This example does that using gmatch():

Thanks Yuri, and Happy Holidays!

I have the waypoint import working, however the coordinates in Mission Planner are different from what was sent to it. Iā€™ve uploaded a picture showing a screen capture from the Message Tab vs the coordinates on the Planner screen. Not sure what to make of it.

Youā€™re still doing arithmetic at too many decimal places, as far as I can tell.

Again, no real help without seeing the code itself.

You were right, I was still doing too many decimals. Fixed that and can import an 8 decimal number using your function.

However there is a problem that I canā€™t resolve. If I provide a waypoints file with 7 decimal coordinates, neither the mission_load.lua nor the mission_edit lua retrieves the coordinates properly (unless I use your function). I have attached my waypoints text file so you can test this. This isnā€™t a rounding issue or precision issue, the imported data just doesnā€™t make sense. If I can save a waypoints file using Mission Planner why canā€™t I retrieve it using Lua?
MM_#01.txt (336 Bytes)

You literally just said ā€œunless I use your function.ā€ Thatā€™s why itā€™s there.

Itā€™s a floating point precision error in the Lua interpreter, plain and simple. Import as a string. Remove the decimal character. Convert to integer. Thatā€™s the workaround.

Fair enough. A good solution for retrieving coordinates from a waypoint file.

The underlying issue seems to be the Lua interpreter, which only handles a maximum of seven digits. Anything more than that is pruned off. This makes it impossible to do math calculations with precision using the Lua interpreter. This applies to any math, not just coordinate calculations.
In the following example, b = tonumber(a) where ā€˜aā€™ is a twelve character string.
image

Let me know if I am misunderstanding the situation.

Thanks Yuri.

Youā€™re not misunderstanding. Iā€™d have to do a little math to determine the true limit, but I suspect signed floating point numbers are stored in 32 bits or less, making them permanently imprecise. An alternative is to do unsigned 32 bit integer math (uint_32t objects), allowing for much larger absolute magnitudes. ArduPilotā€™s C++ codebase uses this tactic frequently to maintain high degrees of precision without the costly requirements of 64-bit arithmetic operations.

EDIT:
Rather than doing the math myself, hereā€™s an article that describes 32-bit floating point precision:

Demystifying Floating Point Precision (demofox.org)
A float has 23 bits of mantissa, and 2^23 is 8,388,608. 23 bits let you store all 6 digit numbers or lower, and most of the 7 digit numbers. This means that floating point numbers have between 6 and 7 digits of precision, regardless of exponent.

Thanks once again for your quick and thorough reply Yuri, much appreciated.

Hi Yuri,

I know this question is not related to the topic, but related to scripting in general (wasnā€™t sure how to contact you directly)

I put a script in ROMFS and now I donā€™t know how to remove it. It was a test script and now whenever I enable scripting it starts running in the background. Very frustrating.

I was able to solve the problem (I thought) by loading a different airframe and the then going back to a clean rover install. I then loaded saved parameters to get back to my original setup for the rover. However when I turn scripting on that test script reappears. It is not in any of the MAVftp directories so must be in ROMFS

Use the parameter SCR_DIR_DISABLE to disable scripts in ROMFS. The only way to remove is by re-flashing firmware.

1 Like

thanks for the quick response Peter!

Hi Peter,
I have disabled the scripting and re-flashed the firmware by loading a different airframe and then rel-loading the rover. Is there a better way to reflash the firmware?

Even with the scripting disabled, this phantom script keeps showing up. I have uninstalled and re-installed Mission Planner as well (latest version),

Any thoughts on how to fix this?