haversine公式最稳妥,因地球近似球体,平面勾股定理误差极大;其数值稳定、无反余弦截断问题、精度够用。

用 haversine 公式算球面距离最稳妥
地球是近似球体,直接用平面勾股定理(sqrt(dx*dx + dy*dy))误差极大——纬度1°在赤道约111km,在北极附近趋近于0。必须用球面几何模型。haversine 是工业级首选:数值稳定、无反余弦大角度截断问题、精度够用(误差通常 acos(sinφ₁·sinφ₂ + cosφ₁·cosφ₂·cosΔλ) 这种原始形式,浮点下容易因舍入导致 acos 输入超出 [-1,1] 范围而返回 nan。
实操建议:
- 输入经纬度务必转为弧度:
lat * M_PI / 180.0,别漏这步 - 用
std::sin/std::cos而非sinf/cosf,双精度对距离计算更安全 - 注意坐标顺序:先纬度(
lat)再经度(lon),和常见地图 API(如 Google Maps)一致,但和某些 GIS 数据(如 GeoJSON 的[lon, lat])相反
std::hypot 配合 haversine 避免中间值溢出
标准 haversine 公式里有 sin²(Δφ/2) + cosφ₁·cosφ₂·sin²(Δλ/2),当两点极近时,sin²(Δφ/2) 极小,直接平方可能下溢归零;当 Δλ 接近 ±180°(如东京 vs 纽约),sin²(Δλ/2) 接近 1,但 cosφ₁·cosφ₂ 很小,乘积精度损失明显。用 std::hypot 可以规避:
double a = std::pow(std::sin(dlat/2), 2)
+ std::cos(lat1) * std::cos(lat2) * std::pow(std::sin(dlon/2), 2);
// 改成:
double a = std::hypot(std::sin(dlat/2),
std::sqrt(std::cos(lat1) * std::cos(lat2)) * std::sin(dlon/2));
虽然严格数学上不等价,但实践中对绝大多数场景(除跨国际日期变更线且高纬度外)误差可忽略,且数值鲁棒性显著提升。
需要更高精度?直接调 GeographicLib 库
如果项目要求亚米级精度(比如测绘、无人机导航),haversine 的球面假设不够——地球是椭球体(WGS84)。此时硬写公式极易出错。直接用成熟库:GeographicLib 的 Geodesic::Inverse 函数:
#include <geographiclib> double distance; GeographicLib::Geodesic::WGS84.Inverse(lat1, lon1, lat2, lon2, distance); </geographiclib>
它默认返回米制距离,支持反向计算方位角、测地线插值。编译时链接 -lGeographicLib,比手撸 Vincenty 公式省心太多。注意:该库不依赖 Boost,头文件即用,但需确认目标平台已安装对应版本(Ubuntu apt 可装 libgeographic-dev)。
别忽略单位、坐标系和时区陷阱
实际项目中最常翻车的不是算法,而是数据源头:
- 检查输入是否真的是 WGS84 坐标(国内有些地图 SDK 返回的是 GCJ-02 偏移坐标,直接算距离会偏移几百米)
- 确认单位是度(degree)而非度分秒(DMS)或弧度(radian)——错误单位会导致结果差 10⁵ 倍
- 经度范围必须是 [-180, 180) 或 [0, 360),若给的是 [-200, -100] 这类非法区间,先做归一化:
lon = fmod(lon + 180.0, 360.0) - 180.0 - 不用考虑时区——距离计算与时间无关,任何时刻结果都一样
真正难的从来不是写对一个公式,而是确保喂给它的两个 double 值,代表的是同一坐标系下、合法范围内的真实地理位置。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











