以数值稳健的方式测试三个点的共线性

问题描述

您可以通过记录线段 (a,{{ 1}}) 必须与 (b,c) 相同,或者注意当且仅当三个点共线时,它们定义的三角形的面积为零.我选择了前一种方法,因为数学看起来更简洁:this answer contains the formula

上面的问题是,虽然当我们将测试转换为代码时数学是完全正确的,但我们发现它的表现很差,因为浮点类型的基本不精确。考虑以下 C++:

a

代码中的三个点如下所示:

enter image description here

实际情况是 b 中的 b 计算结果约为 7.685,这远远超出了我们认为可接受的误差的典型值。这里的问题是公式中的项是绝对坐标中相对长度的乘积,例如c。为了让这个函数表现得更好,我们需要以某种方式对坐标进行归一化。

下面我缩放坐标,使三角形 (a,b,c) 的最长边的长度为 1 个单位:

using point = std::tuple<float,float>;

bool are_collinear(point a,point b,point c,float eps) {
    auto [a_x,a_y] = a;
    auto [b_x,b_y] = b;
    auto [c_x,c_y] = c;

    auto test = (b_x - a_x) * (c_y - a_y) - (c_x - a_x) * (b_y - a_y);
    return std::abs(test) < eps;
}

int main()
{
    point a = { 28.8171,77.9103 };
    point b = { 55.7515,75.5051 };
    point c = { 122.831,69.8003 };

    std::cout << "are_collinear(a,c,0.01) => "
        << (are_collinear(a,0.001) ? "yes\n" : "no\n"); // no

    std::cout << "are_collinear(a,0.1) => "
        << (are_collinear(a,0.1) ? "yes\n" : "no\n"); // no

    std::cout << "are_collinear(a,10) => "
        << (are_collinear(a,10) ? "yes\n" : "no\n"); // yes
}

以上解决了问题,但我有两个问题(1)它是任意的,因为我凭直觉构成了缩放因子;(2)我最初选择斜率方法是因为代码一个-衬垫;新版本明显更长,因此为了简洁而选择此测试不再有意义。

是否有比我的第二个版本更好的方法来执行此测试?我遗漏的代码是否有任何问题?

解决方法

对于具有数字意义的测试,请考虑最长边长度上的(双)区域。这为您提供了中间点与其他两个线的偏差。以长度为单位。

,

如果您的坐标表示为具有有限精度的浮点数,那么就无法为所有可能的情况正确执行共线性测试 - 这意味着您不可避免地会遇到一些情况,这个测试会给你错误的结果。

您可以尝试使用 CGAL 库,该库已使用更复杂的坐标表示解决了此问题。实际上,CGAL 教程中使用了共线性测试来解释此类表示之间的差异 - 请阅读 this 页了解更多信息。