这很可能是由于floating point accuracy problems。
首先,使用的公式是Great circle distance formula:
设φ1,λ1和φ1,λ2为地理经纬度两点1和2,以及Δφ、Δλ它们的绝对差值;那么Δσ,它们之间的中心角,由余弦球面定律给出:
Δσ = arccos ( sin φ<sub>1</sub> ∙ sin φ<sub>2</sub> + cos φ<sub>1</sub> ∙ cos φ<sub>2</sub> ∙ cos (Δλ) ).
距离d,即弧长,对于一个半径为r的球体和以弧度给出的Δσ
<em>d</em> = <em>r</em> Δσ.
现在如果两个点相同,则Δλ = 0,因此cos(Δλ) = cos(0) = 1,第一个公式简化为:
Δσ = arccos (sin φ ∙ sin φ + cos φ ∙ cos φ).
arccos 的参数已变为 Pythagorean trigonometric identity,因此等于 1。
所以上面的简化为:
Δσ = arccos (1).
问题
domain of the arccosine 是:-1 ≤ x ≤ 1,因此值 1 时,我们位于域的边界。
由于 1 的值是多次浮点运算(正弦、余弦、乘法)的结果,因此该值可能不是完全 1,而是类似于 1.0000000000004。这带来了一个问题,因为该值在计算反余弦时 超出范围。数据库引擎对这种情况的反应不同:
SQL Server 将引发异常:
发生了无效的浮点运算。
MySql 只会将表达式评估为 null。
解决办法
以某种方式传递给反余弦的参数应该保持在 -1 ≤ x ≤ 1 的范围内。这样做的一种方法是将参数四舍五入到足够大以保持一定精度的小数位数,但小到足以舍入任何超出此范围的浮点运算。
大多数数据库引擎都有一个 round 函数,可以提供第二个参数来指定要保留的位数,因此 SQL 看起来像这样(保留 6 位小数):
SELECT *
FROM karpool.ride
WHERE Acos(Round(
Sin(Radians(23.065079)) * Sin(Radians(to_lat)) +
Cos(Radians(23.065079)) * Cos(Radians(to_lat)) *
Cos(Radians(to_lon) - Radians(72.511478)),
6
)) * 6371 <= 10;
或者,您可以使用某些数据库引擎提供的函数 greatest 和 least 将任何超出的值变为 1(或 -1):
SELECT *
FROM karpool.ride
WHERE Acos(Greatest(Least(
Sin(Radians(23.065079)) * Sin(Radians(to_lat)) +
Cos(Radians(23.065079)) * Cos(Radians(to_lat)) *
Cos(Radians(to_lon) - Radians(72.511478)),
1), -1)
) * 6371 <= 10;
请注意,SQL Server 不提供 最大/最小 函数。解决这个问题的问题有several answers。