简体   繁体   English

最小化累积浮点算术误差

[英]Minimising cumulative floating point arithmetic error

I have a 2D convex polygon in 3D space and a function to measure the area of the polygon.我在 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;
    }
}

To test that this method works at all orientations of the polygon I had my program rotate it a little bit each iteration and calculate the area.为了测试此方法是否适用于多边形的所有方向,我让我的程序在每次迭代时稍微旋转它并计算面积。 Like so...像这样...

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());

My method seems correct when testing with a 20x20 square.在使用 20x20 正方形进行测试时,我的方法似乎是正确的。 However the rotate method (a method in the Vector3 class) seems to introduce some error into the position of each vertex in the polygon, which affects the area calculation.但是rotate方法(Vector3类中的一个方法)好像给多边形每个顶点的position引入了一些误差,影响了面积计算。 Here is the Vector3.rotate() method这是 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);
}

Here is the output for my program in the format "iteration: area":这是我的程序的 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

Since this is intended to eventually be for a physics engine I would like to know how I can minimise the cumulative error since the Vector3.rotate() method will be used on a very regular basis.由于这最终将用于物理引擎,因此我想知道如何最大限度地减少累积误差,因为将定期使用 Vector3.rotate() 方法。

Thanks!谢谢!

A couple of odd notes:一些奇怪的笔记:

  • The error is proportional to the amount rotated.误差与旋转量成正比。 ie. IE。 bigger rotation per iteration -> bigger error per iteration.每次迭代更大的旋转 - >每次迭代更大的错误。

  • There is more error when passing doubles to the rotate function than when passing it floats.将 double 传递给 rotate function 时比传递浮点数时出错更多。

You'll always have some cumulative error with repeated floating point trig operations — that's just how they work.重复的浮点触发操作总是会产生一些累积误差——这就是它们的工作原理。 To deal with it, you basically have two options:要处理它,您基本上有两种选择:

  1. Just ignore it.忽略它。 Note that, in your example, after 20,000 iterations(.) the area is still accurate down to 13 decimal places, That's not bad, considering that doubles can only store about 16 decimal places to begin with .请注意,在您的示例中,经过 20,000 次迭代 (.) 后,该区域仍然精确到小数点后 13 位,这还不错,考虑到双打只能存储大约 16 位小数以开始

    Indeed, plotting your graph, the area of your square seems to be going down more or less linearly:事实上,绘制你的图表,你的正方形面积似乎或多或少呈线性下降:
    阴谋
    This makes sense, assuming that the effective determinant of your approximate rotation matrix is about 1 − 3.417825 × 10 -18 , which is well within normal double precision floating point error range of one.这是有道理的,假设您的近似旋转矩阵的有效行列式约为 1 − 3.417825 × 10 -18 ,这完全在正常的双精度浮点误差范围内。 If that's the case, the area of your square would continue a very slow exponential decay towards zero, such that you'd need about如果是这样的话,你的正方形面积将继续以非常缓慢的指数衰减到零,这样你就需要大约two billion (2 × 10 9 )二十亿 (2 × 10 9 ) 7.3 × 10 14 iterations to get the area down to 399. Assuming 100 iterations per second, that's about 7.3 × 10 14次迭代使面积下降到 399。假设每秒迭代 100 次,大约是seven and a half months七个半月230 thousand years. 23万年。

    Edit: When I first calculated how long it would take for the area to reach 399, it seems I made a mistake and somehow managed to overestimate the decay rate by a factor of about 400,000(.).编辑:当我第一次计算该区域达到 399 需要多长时间时,我似乎犯了一个错误,并且以某种方式设法高估了衰减率大约 400,000(.) 倍。 I've corrected the mistake above.我已经纠正了上面的错误。

  2. If you still feel you don't want any cumulative error, the answer is simple: don't iterate floating point rotations.如果您仍然觉得不需要任何累积误差,答案很简单:不要迭代浮点旋转。 Instead, have your object store its current orientation in a member variable, and use that information to always rotate the object from its original orientation to its current one.相反,让您的 object 将其当前方向存储在一个成员变量中,并使用该信息始终将 object 从其原始方向旋转到当前方向。

    This is simple in 2D, since you just have to store an angle.这在 2D 中很简单,因为您只需要存储一个角度。 In 3D, I'd suggest storing either a quaternion or a matrix, and occasionally rescaling it so that its norm / determinant stays approximately one (and, if you're using a matrix to represent the orientation of a rigid body, that it remains approximately orthogonal ).在 3D 中,我建议存储四元数或矩阵,并偶尔重新缩放它,使其范数/行列式保持大约为一(并且,如果您使用矩阵来表示刚体的方向,它仍然近似正交)。

    Of course, this approach won't eliminate cumulative error in the orientation of the object, but the rescaling does ensure that the volume, area and/or shape of the object won't be affected.当然,这种方法不会消除 object方向的累积误差,但重新缩放确实确保 object 的体积、面积和/或形状不会受到影响。

You say there is cumulative error but I don't believe there is (note how your output desn't always go down) and the rest of the error is just due to rounding and loss of precision in a float.你说有累积错误,但我不相信有(注意你的 output 并不总是 go 向下)和错误的 rest 只是由于四舍五入和浮点数精度损失。

I did work on a 2d physics engine in university (in java) and found double to be more precise (of course it is see oracles datatype sizes我在大学(在 Java 中)做过一个 2d 物理引擎,发现 double 更精确(当然它是oracles datatype sizes

In short you will never get rid of this behaviour you just have to accept the limitations of precision简而言之,您永远无法摆脱这种行为,您只需要接受精度的限制

EDIT:编辑:

Now I look at your.area function there is possibly some cumulative due to现在我看看你的.area function 可能有一些累积由于

+= cross.magnitude

but I have to say that whole function looks a bit odd.但我不得不说整个 function 看起来有点奇怪。 Why does it need to know the previous vertices to calculate the current area?为什么它需要知道以前的顶点来计算当前面积?

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

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