两个圆相切当且仅当圆心距等于半径和或半径差的绝对值;需校验半径非负,用平方距离与容差EPS避免浮点误差,统一判定外切与内切。

判断两个圆是否相切:核心是距离与半径关系
两个圆在二维平面中相切,本质只有两种情况:外切(两圆在切点处彼此接触但不重叠)和内切(一圆在另一圆内部,仅一点接触)。二者统一的数学条件是:圆心距等于两半径之和(外切)或等于两半径之差的绝对值(内切)。因此,只需计算圆心距离 d,再比对 abs(r1 - r2) 和 r1 + r2 即可。
注意浮点数比较必须用误差容限(EPS),不能直接用 ==。常见错误是忽略内切时大圆小圆顺序无关,必须用 abs(r1 - r2);另一个坑是未处理退化情况(如半径为 0 或负数),建议提前校验。
- 设圆 A 圆心
(x1, y1)、半径r1,圆 B 圆心(x2, y2)、半径r2 - 先做参数检查:
r1 >= 0且r2 >= 0,否则无几何意义 - 计算平方距离避免开方误差和性能损耗:
dx = x1 - x2; dy = y1 - y2; d_sq = dx*dx + dy*dy - 定义容差:
const double EPS = 1e-9;
C++ 实现相切判定函数
推荐封装为布尔函数,返回 true 当且仅当发生外切或内切(不含相交或分离)。关键在于用平方形式避免 sqrt 引入额外误差,并统一用平方容差比较。
bool isTangent(double x1, double y1, double r1,
double x2, double y2, double r2) {
if (r1 < 0 || r2 < 0) return false;
double dx = x1 - x2, dy = y1 - y2;
double d_sq = dx * dx + dy * dy;
double sum_r = r1 + r2;
double diff_r = std::abs(r1 - r2);
// 比较平方距离与 (r1±r2)²,用 EPS² 避免开方
double eps_sq = 1e-18;
return std::abs(d_sq - sum_r * sum_r) < eps_sq ||
std::abs(d_sq - diff_r * diff_r) < eps_sq;
}
这个写法规避了 sqrt 的精度损失和运行开销;eps_sq 对应 1e-9 容差的平方,更稳妥。若输入含大量整数坐标,也可用 long long 算平方距离防溢出。
立即学习“C++免费学习笔记(深入)”;
区分内切与外切:需要额外逻辑
如果业务需要知道是哪种相切(比如物理引擎中碰撞响应不同),不能只靠距离相等——需结合圆心距与半径关系进一步判断:
- 外切成立条件:
d_sq == (r1 + r2)²(在容差内) - 内切成立条件:
d_sq == (r1 - r2)²且r1 != r2;若r1 == r2,则内切退化为同心圆不相切(除非d_sq == 0,此时两圆重合,不算相切) - 特别注意:当
r1 == r2且d_sq < EPS,两圆完全重合,不属于相切(几何定义要求“恰好一个公共点”)
所以严格来说,内切还隐含前提:一圆完全在另一圆内部(即 d + min(r1,r2) < max(r1,r2)),但该条件等价于 d_sq < (r1 - r2)² + EPS —— 而相切时取等号,因此只要满足内切距离条件且半径不等,即自动满足位置关系。
容易被忽略的边界与精度问题
实际工程中栽跟头的往往不是公式本身,而是这些细节:
-
r1或r2为0:点圆是否与另一圆相切?按标准定义,点圆(半径 0)与圆相切当且仅当点在圆周上,即d_sq == r²;但多数场景应显式排除r == 0,除非业务明确支持 - 使用
float代替double:容差需相应调至1e-5左右,且平方误差放大更快,建议统一用double - 坐标值极大(如
1e15)时,dx*dx可能溢出double;此时应先归一化或用相对误差判断 - 多个圆批量检测时,避免重复计算
dx/dy,可预存向量差
真正麻烦的从来不是“怎么写”,而是“什么算数、什么不算”——比如两个半径极接近的圆,圆心距略小于半径差,数值上可能落在容差带边缘,这时得看你的业务能否接受模糊判定。


















