如何正确划分微小的双精度数而不出现精度误差?

时间:2012-05-06 12:43:27

标签: actionscript-3 floating-point double division floating-accuracy

我正在尝试诊断并修复一个可归结为X / Y的错误,当X和Y很小时会产生不稳定的结果:

enter image description here

在这种情况下,cx和patharea都会顺利增加。它们的比例在高数字时是平滑的渐近线,但对于“小”数字则不稳定。显而易见的第一个想法是我们达到浮点精度的极限,但实际数字本身远不及它。 ActionScript“Number”类型是IEE 754双精度浮点数,因此应该有15个十进制数字的精度(如果我读得正确)。

分母(patharea)的一些典型值:

0.0000000002119123
0.0000000002137313
0.0000000002137313
0.0000000002155502
0.0000000002182787
0.0000000002200977
0.0000000002210072

分子(cx):

0.0000000922932995
0.0000000930474444
0.0000000930582124
0.0000000938123574
0.0000000950458711
0.0000000958000159
0.0000000962901528
0.0000000970442977
0.0000000977984426

每一个都单调增加,但如上所述,这个比例是混乱的。

在更大的数字上,它会平稳地变为平滑的双曲线。

所以,我的问题是:当你需要分开时,处理非常小数字的正确方法是什么?

我想过提前将分子和/或分母乘以1000,但不能完全解决。

有问题的实际代码是recalculate()函数here。它计算多边形的质心,但是当多边形很小时,质心在该位置周围不规则地跳跃,并且可能与多边形相距很远。上面的数据系列是以一致的方向移动多边形的一个节点的结果(手动,这就是为什么它不是非常平滑)。

这是Adobe Flex 4.5。

2 个答案:

答案 0 :(得分:19)

我认为问题很可能是由代码中的以下行引起的:

sc = (lx*latp-lon*ly)*paint.map.scalefactor;

如果您的多边形非常小,那么lxlon几乎相同,lylatp也是如此。与结果相比,它们都非常大,所以你要减去两个几乎相等的数字。

为了解决这个问题,我们可以利用以下这样的事实:

x1*y2-x2*y1 = (x2+(x1-x2))*y2 - x2*(y2+(y1-y2))
            = x2*y2 + (x1-x2)*y2 - x2*y2 - x2*(y2-y1)
            = (x1-x2)*y2 - x2*(y2-y1)

所以,试试这个:

dlon = lx - lon
dlat = ly - latp
sc = (dlon*latp-lon*dlat)*paint.map.scalefactor;

该值在数学上是相同的,但是这些项的数量级要小一些,因此误差也应该小一个数量级。

答案 1 :(得分:3)

Jeffrey Sax正确地确定了基本问题 - 通过组合比(最终)大于最终结果的术语而导致的精度损失。 建议的重写消除了部分问题 - 显然对于实际情况是足够的,给出了快乐的反应。

然而,您可能会发现,如果多边形再次(更大)变得更小和/或更远离原点,则会再次出现不准确性。在重写的公式中,这些术语仍然比它们的差异要大得多。

此外,算法中存在另一个“组合 - 大型和可比较数字与不同符号”问题。在多边形边缘上的迭代的后续循环中的各种“sc”值有效地组合成比单独的sc(i)小(多)的最终数。 (如果你有一个凸多边形,你会发现有一个连续的正值序列,一个连续的负值序列,在非凸多边形中,负数和正数可能交织在一起)。

算法正在做的有效的是通过添加由边和原点跨越的三角形区域来计算多边形的区域,其中一些项是负的(每当边沿顺时针移动时,从原点)和一些正面(逆时针走过边缘)。

通过在多边形的一个角上定义原点,比如说(lx,ly)然后添加由边和那个角跨越的三角形表面(所以​​:转换),你摆脱了所有精度损失问题lon到(lon-lx)和latp到(latp-ly) - 除了你需要处理两个三角形之外的额外奖励,因为显然连接到所选原点的边缘会产生零个表面。

对于所有区域部分。对于质心部分,您当然必须将结果“转换回”原始帧,即在末尾添加(lx,ly)。