几何尺寸与公差论坛

 找回密码
 注册
查看: 35|回复: 7

如何正确计算 3D 距离?

  [复制链接]
发表于 5 天前 | 显示全部楼层 |阅读模式
如何正确计算 3D 距离?
 楼主| 发表于 5 天前 | 显示全部楼层
#include <cmath>

static double Distance(const double p1[], const double p2[]) {
    if (NULL == p1 || NULL == p2) {
        return 0;
    }
    double dx = p1[0] - p2[0];
    double dy = p1[1] - p2[1];
    double dz = p1[2] - p2[2];

    // 先计算 2D 距离,然后再加上 z 轴的影响
    return std::hypot(std::hypot(dx, dy), dz);
}
 楼主| 发表于 5 天前 | 显示全部楼层
为什么 点集会有误差?
double ddot(int n, const double x[], const double y[])
{
    double dot = 0.;
    if (nullptr == x || nullptr == y)
    {
        return dot;
    }
    if (n < 1)
        return 0.;
    for (int i = 0; i < n; i++)
        dot += x[i] * y[i];  // <-- 这里可能会有浮点误差累积
    return dot;
}
 楼主| 发表于 5 天前 | 显示全部楼层
误差来源
浮点数舍入误差

每次 x * y 计算时,都会有 微小的舍入误差。

由于 dot += x * y 累加多个值,误差会不断积累。

数值较大时,精度丢失

如果 x 和 y 的数值很大(例如 1e+15 级别),而它们的 小数部分变化较小,那么在累加过程中,小的变化可能会被忽略,导致精度丢失。

浮点数的加法 (Associativity Problem)

在浮点运算中,加法不是严格满足结合律的:

(
&#119886;
+
&#119887;
)
+
&#119888;

&#119886;
+
(
&#119887;
+
&#119888;
)
(a+b)+c

=a+(b+c)
如果 x * y 计算出的值跨度很大(比如 1e-10 和 1e+10 同时累加),小的数值可能会被忽略(即 Catastrophic Cancellation 现象)。
 楼主| 发表于 5 天前 | 显示全部楼层
如何优化?
&#9989; 1. 使用 Kahan Summation Algorithm
Kahan 求和算法 能够减少累积误差:

cpp
Copy
Edit
double ddot(int n, const double x[], const double y[])
{
    double dot = 0.0;
    double c = 0.0; // 补偿误差
    if (nullptr == x || nullptr == y)
    {
        return dot;
    }
    if (n < 1)
        return 0.;

    for (int i = 0; i < n; i++) {
        double product = x * y;
        double y = product - c;  
        double t = dot + y;  
        c = (t - dot) - y;  
        dot = t;  
    }
    return dot;
}
&#128313; 优势

减少精度损失:用 c 变量补偿小数部分,防止被丢失。

更适用于长向量:当 n 很大时,能降低误差累积。
 楼主| 发表于 5 天前 | 显示全部楼层
2. 使用 std::fma()(Fused Multiply-Add, FMA)
如果你的 CPU 支持 FMA(Intel AVX2 及以上),可以用 std::fma() 进行更精准的计算:

cpp
Copy
Edit
#include <cmath> // 需要 C++11 以上

double CommonMathUtil::ddot(int n, const double x[], const double y[])
{
    double dot = 0.;
    if (nullptr == x || nullptr == y)
    {
        return dot;
    }
    if (n < 1)
        return 0.;

    for (int i = 0; i < n; i++) {
        dot = std::fma(x[i], y[i], dot);  // 直接计算 x[i] * y[i] + dot,避免中间值丢失
    }
    return dot;
}
&#128313; 优势

std::fma(a, b, c) 不会单独计算 a*b 后再加 c,而是直接计算 (a*b) + c,减少舍入误差。

速度更快:现代 CPU 直接支持 FMA 指令,比 + 和 * 分开计算更快。
 楼主| 发表于 5 天前 | 显示全部楼层
3. 采用 OpenMP 并行计算(适用于大向量)
如果 n 很大,可以用 OpenMP 并行计算点积:

cpp
Copy
Edit
#include <omp.h>

double ddot(int n, const double x[], const double y[])
{
    double dot = 0.;
    if (nullptr == x || nullptr == y || n < 1)
        return dot;

    #pragma omp parallel for reduction(+:dot)
    for (int i = 0; i < n; i++) {
        dot += x * y;
    }
    return dot;
}
&#128313; 优势

并行计算,提高性能(尤其适用于 n > 1e6 的情况)。

减少串行累积误差,因为 OpenMP 采用多线程分块计算,避免单线程累计误差。
 楼主| 发表于 5 天前 | 显示全部楼层
结论
直线 B 位置不同(linePoint 变动)会影响计算结果!
如果 A 和 B 不是共面直线(一般 3D 直线不会共面),那么 distance difference 不是固定的,而是取决于 linePoint!
选择 linePoint 时,建议选 A 的中点,以减少误差和变化幅度。

所以,你的结果变化是正常的,因为在 3D 空间里,点到直线的距离不是固定的,它取决于计算时选取的 linePoint!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|Archiver|小黑屋|几何尺寸与公差论坛

GMT+8, 2025-4-2 05:41 , Processed in 0.039265 second(s), 19 queries .

Powered by Discuz! X3.4 Licensed

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表