简体   繁体   中英

Haversine's Formula and Python 3 - Math Domain Error

I'm trying to implement Haversine's formula to determine whether a given location's latitude and longitude is within a specified radius. I used a formula detailed here:
Calculate distance between two latitude-longitude points? (Haversine formula)

I am experiencing a Math Domain Error with the following re-producible input, it does not happen all the time, but often enough to make me think I've written in-correct code:

from math import atan2, sqrt, sin, cos

# All long / lat values are in radians and of type float
centerLongitude = -0.0391412861306467
centerLatitude = 0.9334153362515779

inputLatitudeValue = -0.6096173085842176
inputLongitudeValue = 2.4190393564390438

longitudeDelta = inputLongitudeValue - centerLongitude # 2.4581806425696904
latitudeDelta = inputLatitudeValue - centerLatitude # -1.5430326448357956

a = (sin(latitudeDelta / 2) ** 2 + cos(centerLatitude) * cos(centerLongitude) 
     * sin(longitudeDelta / 2) ** 2)
# a = 1.0139858858386017

c = 2 * atan2(sqrt(a), sqrt(1 - a)) # Error occurs on this line

# Check whether distance is within our specified radius below

You cannot use sqrt on negative numbers:

>>> sqrt(-1)   
ValueError: math domain error

use cmath.srt :

>>> import cmath
>>> cmath.sqrt(-1)
1j

in your case:

>>> a = 1.0139858858386017
>>> sqrt(1-a)
ValueError: math domain error

Speaking generally, and said simply... the variable a must be protected. It must never be greater than 1.0, and it must never be less than 0.0, and normally, with clean data, it should be properly in this range.

The problem is with how the common popular computer implementation of floating point arithmetic does its approximation and roundings, and how those results can sometimes be out of range or out of domain for a builtin math function. It is not particularly common that the combination of operations results in a number that is out of domain for a following math function, but in those cases where it does occur, depending on the algorithm, it is consistently reproducable, and needs to be accounted for and mitigated, beyond a normal theoretical algorithm intuition. What we code in computers is subject to how theoretical concepts have been implemented for the software and hardware. On paper, with pencil, we still must have guidelines for when and how to round floating point math results. Computer implementations are no different, but sometimes we are blissfully unaware of such things going on under the hood. Popular computer implementations not only need to know at what precision to round, but also are dealing with how and when to approximate and round in the conversion to and from the binary number representations that are actually calculated at the machine level.

Regarding the haversine formula, what a am I speaking about?

as seen in this version of code (for reference):

import math

a = math.sin(dlat / 2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))

In this above example, the a is not properly protected. It is a lurking problem waiting to crash the c calculation on the next line, in certain conditions presented particularly to the haversine formula.

If some latlon combination of data results in

a = 1.00000000000000000000000000000000000001

the following c calculation with cause an error.

If some latlon combination of data results in

a = -0.00000000000000000000000000000000000000000000001

the following c calculation will cause an error.

It is the floating point math implementation of your language/platform and its method of rounding approximation that can cause the rare but actual and consistently repeatable ever so slightly out of domain condition that causes the error in an unprotected haversine coding.

Years ago I did a three day brute force test of relative angular distances between 0 and 1, and 179 and 180, with VERY small step values. The radius of the sphere is one, a unit sphere, so that radius values are irrelevant. Of course, finding approximate distances on the surface of the sphere in any unit other than angular distance would require including the radius of the sphere in its units. But I was testing the haversine logic implementation itself, and a radius of 1 eliminated a complication. When the relative angular distances are 0 -1, or 179-180, these are the conditions where haversine can have difficulties, implemented with popular floating point implementations that involve converting to and from binary at a low system level, if the a is not protected. Haversine is supposed to be well conditioned for small angular distances, theoretically, but a machine or software implementation of FPA (floating point arithmetic) is not always precisely cooperative with the ideals of a spherical geometry theory. After 3 days of my brute force test, there were logged thousands of latlon combinations that would crash the unprotected popularly posted haversine formula, because the a was not protected. You must protect the a . If it goes above 1.0, or below 0.0, even the very slightest bit, all one need do is simply test for that condition and nudge it back into range. Simple.

I protected an a of -0.000000000000000000002 or in other words, if a < 0.0, by reassigning the value 0.0 to it, and also setting another flag that I could inspect so I would know if a protective action had been necessary for that data.

I protected an a of 1.000000000000000000002 or in other words, if a > 1.0, by reassigning the value 1.0 to it, and also setting another flag that I could inspect so I would know if a protective action had been necessary for that data.

It is a simple 2 extra lines before the c calculation. You could squeeze it all on one line. The protective line(s) of code go after the a calculation, and before the c calculation. Protect your a .

Are you then loosing precision with those slight nudges? No more than what the floating point math with that data is already introducing with its approximations and rounding. It crashed with data that should not have crashed a pure theoretical algorithm, one that doesn't have FPA rare issues. Simply protect the a , and this should mitigate those errors, with haversine in this form. There are alternatives to haversine, but haversine is completely suitable for many uses, if one understands where it is well suited. I use it for skysphere calculations where the ellipsoid shape of the earth has nothing to do with anything. Haversine is simple and fast. But remember to protect your a .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM