繁体   English   中英

最小化累积浮点算术误差

[英]Minimising cumulative floating point arithmetic error

我在 3D 空间中有一个 2D 凸多边形和一个 function 来测量多边形的面积。

public double area() {
    if (vertices.size() >= 3) {
        double area = 0;
        Vector3 origin = vertices.get(0);
        Vector3 prev = vertices.get(1).clone();
        prev.sub(origin);
        for (int i = 2; i < vertices.size(); i++) {
            Vector3 current = vertices.get(i).clone();
            current.sub(origin);
            Vector3 cross = prev.cross(current);
            area += cross.magnitude();
            prev = current;
        }
        area /= 2;
        return area;
    } else {
        return 0;
    }
}

为了测试此方法是否适用于多边形的所有方向,我让我的程序在每次迭代时稍微旋转它并计算面积。 像这样...

Face f = poly.getFaces().get(0);
        for (int i = 0; i < f.size(); i++) {
            Vector3 v = f.getVertex(i);
            v.rotate(0.1f, 0.2f, 0.3f);
        }
        if (blah % 1000 == 0)
            System.out.println(blah + ":\t" + f.area());

在使用 20x20 正方形进行测试时,我的方法似乎是正确的。 但是rotate方法(Vector3类中的一个方法)好像给多边形每个顶点的position引入了一些误差,影响了面积计算。 这是 Vector3.rotate() 方法

public void rotate(double xAngle, double yAngle, double zAngle) {
    double oldY = y;
    double oldZ = z;
    y = oldY * Math.cos(xAngle) - oldZ * Math.sin(xAngle);
    z = oldY * Math.sin(xAngle) + oldZ * Math.cos(xAngle);

    oldZ = z;
    double oldX = x;
    z = oldZ * Math.cos(yAngle) - oldX * Math.sin(yAngle);
    x = oldZ * Math.sin(yAngle) + oldX * Math.cos(yAngle);

    oldX = x;
    oldY = y;
    x = oldX * Math.cos(zAngle) - oldY * Math.sin(zAngle);
    y = oldX * Math.sin(zAngle) + oldY * Math.cos(zAngle);
}

这是我的程序的 output,格式为“迭代:区域”:

0:  400.0
1000:   399.9999999999981
2000:   399.99999999999744
3000:   399.9999999999959
4000:   399.9999999999924
5000:   399.9999999999912
6000:   399.99999999999187
7000:   399.9999999999892
8000:   399.9999999999868
9000:   399.99999999998664
10000:  399.99999999998386
11000:  399.99999999998283
12000:  399.99999999998215
13000:  399.9999999999805
14000:  399.99999999998016
15000:  399.99999999997897
16000:  399.9999999999782
17000:  399.99999999997715
18000:  399.99999999997726
19000:  399.9999999999769
20000:  399.99999999997584

由于这最终将用于物理引擎,因此我想知道如何最大限度地减少累积误差,因为将定期使用 Vector3.rotate() 方法。

谢谢!

一些奇怪的笔记:

  • 误差与旋转量成正比。 IE。 每次迭代更大的旋转 - >每次迭代更大的错误。

  • 将 double 传递给 rotate function 时比传递浮点数时出错更多。

重复的浮点触发操作总是会产生一些累积误差——这就是它们的工作原理。 要处理它,您基本上有两种选择:

  1. 忽略它。 请注意,在您的示例中,经过 20,000 次迭代 (.) 后,该区域仍然精确到小数点后 13 位,这还不错,考虑到双打只能存储大约 16 位小数以开始

    事实上,绘制你的图表,你的正方形面积似乎或多或少呈线性下降:
    阴谋
    这是有道理的,假设您的近似旋转矩阵的有效行列式约为 1 − 3.417825 × 10 -18 ,这完全在正常的双精度浮点误差范围内。 如果是这样的话,你的正方形面积将继续以非常缓慢的指数衰减到零,这样你就需要大约二十亿 (2 × 10 9 ) 7.3 × 10 14次迭代使面积下降到 399。假设每秒迭代 100 次,大约是七个半月 23万年。

    编辑:当我第一次计算该区域达到 399 需要多长时间时,我似乎犯了一个错误,并且以某种方式设法高估了衰减率大约 400,000(.) 倍。 我已经纠正了上面的错误。

  2. 如果您仍然觉得不需要任何累积误差,答案很简单:不要迭代浮点旋转。 相反,让您的 object 将其当前方向存储在一个成员变量中,并使用该信息始终将 object 从其原始方向旋转到当前方向。

    这在 2D 中很简单,因为您只需要存储一个角度。 在 3D 中,我建议存储四元数或矩阵,并偶尔重新缩放它,使其范数/行列式保持大约为一(并且,如果您使用矩阵来表示刚体的方向,它仍然近似正交)。

    当然,这种方法不会消除 object方向的累积误差,但重新缩放确实确保 object 的体积、面积和/或形状不会受到影响。

你说有累积错误,但我不相信有(注意你的 output 并不总是 go 向下)和错误的 rest 只是由于四舍五入和浮点数精度损失。

我在大学(在 Java 中)做过一个 2d 物理引擎,发现 double 更精确(当然它是oracles datatype sizes

简而言之,您永远无法摆脱这种行为,您只需要接受精度的限制

编辑:

现在我看看你的.area function 可能有一些累积由于

+= cross.magnitude

但我不得不说整个 function 看起来有点奇怪。 为什么它需要知道以前的顶点来计算当前面积?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM