Java 最小化累积浮点算术错误

Java 最小化累积浮点算术错误,java,math,vector,Java,Math,Vector,我有一个3D空间中的2D凸多边形和一个测量多边形面积的函数 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 =

我有一个3D空间中的2D凸多边形和一个测量多边形面积的函数

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;
    }
}
以下是我的程序的输出,格式为“迭代:区域”:

由于这最终将用于物理引擎,我想知道如何将累积误差降至最低,因为Vector3.rotate()方法将定期使用

谢谢

几个奇怪的注释:

  • 误差与旋转量成正比。即:每次迭代的旋转次数越大->每次迭代的误差越大

  • 将double传递给rotate函数时的错误比传递float函数时的错误更多


你说有累积误差,但我不相信有累积误差(注意你的输出数据并不总是下降),其余的误差只是由于四舍五入和浮点精度的损失

我在大学里做过一个2d物理引擎的工作(java),发现double更精确(当然是这样)

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

编辑:

现在我看一下你的面积函数,可能是由于

+= cross.magnitude
但是我不得不说,整个函数看起来有点奇怪。为什么它需要知道前面的顶点来计算当前面积?

重复的浮点三角运算总是会有一些累积误差-这就是它们的工作原理。要处理它,基本上有两个选项:

  • 忽略它。请注意,在您的示例中,经过20000次迭代(!)后,面积仍然精确到小数点后13位。考虑到这一点,这还不错

    事实上,绘制图形时,正方形的面积似乎或多或少呈线性下降:

    假设近似值的有效值约为1,这是有意义的−3.417825×10-18,这在正常的双精度浮点误差范围1内。如果是这种情况,平方面积将继续以非常缓慢的指数衰减方式向零递减,因此需要大约20亿(2×109)7.3×1014次迭代,将面积减少到399。假设每秒迭代100次,大约7个半月,即23万年

    编辑:当我第一次计算面积达到399需要多长时间时,似乎我犯了一个错误,不知何故高估了衰变率大约400000(!)。我已经纠正了上面的错误

  • 如果您仍然不希望出现任何累积错误,答案很简单:不要迭代浮点旋转。相反,让对象将其当前方向存储在成员变量中,并使用该信息始终将对象从其原始方向旋转到其当前方向

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

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


  • 使用浮点数的优点可能是由于某些截断/舍入。这可能暗示某种舍入方案会减少错误。此外,这些数字并不特别需要担心。谢谢,我认为你是对的,我将不得不忍受这些错误。我认为错误仍然是累积的,因为它是旋转的每次迭代都会产生一个越来越不准确的位置向量。面积函数从第一个顶点到顶点i和i+1取向量。然后取由这两个向量组成的平行四边形的面积(计算结果是叉积的大小)然后就是对每个i求和,然后在最后取一半,因为我们想要的是三角形的和,而不是平行四边形的和。存储之前的顶点只是稍微加快了速度:我想我看到了-实际上你看到的是导致混沌理论和术语“蝴蝶效应”的原因-一位计算机科学家用这个不精确的to显示变化非常小的复杂系统会很快变得混沌@null0pointer这很酷,我不知道混沌理论是从计算机科学中产生的:)@PaulSullivan-注意,误差本质上是一个随机游动,它会在多个维度上发散。而且,是的,误差是累积的。非常好而且有根据的答案——比我的“门外汉”理解要好得多。哇,感谢你花时间仔细观察这个区域的衰变,我甚至没有想到要花多长时间。一旦完全模拟发生,旋转调用的速度将永远不会达到每秒100次。此外,我已经将一个多面体存储为一组顶点的基本坐标,以及向量3中每个轴的方向。我没有建立逻辑上的联系,这将减轻任何积累。回答得很好。抱歉@PaulSullivan,我得把正确答案改成这个。
    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);
    }
    
    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
    
    += cross.magnitude