繁体   English   中英

使用浮点数进行最精确的线交叉纵坐标计算?

[英]Most accurate line intersection ordinate computation with floats?

我正在计算给定横坐标x的直线上的点的纵坐标y。 该线由其两个端点坐标(x0,y0)(x1,y1)定义。 端点坐标是浮点数,计算必须以浮点精度完成,以便在GPU中使用。

数学,以及天真的实现,都是微不足道的。

设t =(x-x0)/(x1-x0),则y =(1-t)* y0 + t * y1 = y0 + t *(y1-y0)。

问题是当x1 - x0很小时。 结果将引入取消错误。 当与x - x0中的一个组合时,在分区中我期望t中存在显着误差。

问题是,是否存在另一种方法来更准确地确定y?

即我应该首先计算(x - x0)*(y1 - y0),然后除以(x1 - x0)吗?

差异y1 - y0总是很大。

在很大程度上,你的根本问题是根本的。 当(x1-x0)很小时,意味着x1和x0的尾数中只有几位不同。 而且,通过扩展,x0和x1之间只有一个有限数量的浮点数。 例如,如果只有尾数的低4位不同,则它们之间最多有14个值。

在您的最佳算法中, t term表示这些较低位。 并且继续或示例,如果x0和x1相差4位,则t也可以仅占用16个值。 这些可能值的计算相当稳健。 无论您是在计算3E0 / 14E0还是3E-12 / 14E-12,结果都将接近3/14的数学值。

由于0 <= t <= 1,因此您的公式具有y0 <= y <= y1的额外优势

(我假设您对浮点表示有足够的​​了解,因此“(x1-x0)很小”实际上意味着“相对于x1和x0本身的值很小”。当x0时,1E-1的差异很小= 1E3但是如果x0 = 1E-6则很大)

你可以看看Qt的“QLine”(如果我没记错的话)来源; 他们已经实现了一个交叉点确定算法,该算法来自一个“图形宝石”书籍(参考文献必须在代码注释中,这本书是在几年前的EDonkey上),反过来,它对一个适用性有一些保证。给定屏幕分辨率时,使用给定的位宽进行计算(如果我没有错,则使用定点算术)。

如果你有可能这样做,你可以在计算中引入两种情况,这取决于abs(x1-x0)<abs(y1-y0)。 在垂直情况下,abs(x1-x0)<abs(y1-y0),从y计算x而不是从x计算y。

编辑。 另一种可能性是使用二分法搜索的变体逐位获得结果。 这将会变慢,但在极端情况下可能会改善结果。

// Input is X
xmin = min(x0,x1);
xmax = max(x0,x1);
ymin = min(y0,y1);
ymax = max(y0,y1);
for (int i=0;i<20;i++) // get 20 bits in result
{
  xmid = (xmin+xmax)*0.5;
  ymid = (ymin+ymax)*0.5;
  if ( x < xmid ) { xmax = xmid; ymax = ymid; } // first half
  else { xmin = xmid; ymin = ymid; } // second half
}
// Output is some value in [ymin,ymax]
Y = ymin;

我已经实现了一个基准程序来比较不同表达式的效果。

我使用双精度计算y,然后使用具有不同表达式的单精度计算y。

以下是测试的表达式:

inline double getYDbl( double x, double x0, double y0, double x1, double y1 )
{
    double const t = (x - x0)/(x1 - x0);
    return y0 + t*(y1 - y0);
} 

inline float getYFlt1( float x, float x0, float y0, float x1, float y1 )
{
    double const t = (x - x0)/(x1 - x0);
    return y0 + t*(y1 - y0);
} 

inline float getYFlt2( float x, float x0, float y0, float x1, float y1 )
{
    double const t = (x - x0)*(y1 - y0);
    return y0 + t/(x1 - x0);
} 

inline float getYFlt3( float x, float x0, float y0, float x1, float y1 )
{
    double const t = (y1 - y0)/(x1 - x0);
    return y0 + t*(x - x0);
} 

inline float getYFlt4( float x, float x0, float y0, float x1, float y1 )
{
    double const t = (x1 - x0)/(y1 - y0);
    return y0 + (x - x0)/t;
} 

我计算了双精度结果和单精度结果之间差异的平均值和stdDev。

结果是平均超过1000和10K随机值集没有。 我使用icc编译器有和没有优化以及g ++。

请注意,我必须使用isnan()函数来过滤掉伪造的值。 我怀疑这些结果来自于差异或分裂的下溢。

我不知道编译器是否重新排列了表达式。

无论如何,该测试的结论是表达式的上述重新排列对计算精度没有影响。 错误保持不变(平均)。

如果您的源数据已经是浮点数,那么您已经存在基本的不准确性。

为了进一步解释,想象一下你是否以图形方式进行此操作。 你有一张2D纸质方格纸,并标有2个点。

案例1:这些点非常准确,并且标有非常锋利的铅笔。 它很容易绘制连接它们的线,并且很容易得到给定的x(反之亦然)。

案例2:这些点标有一个大的毡尖笔,就像一个宾果标记。 显然,您绘制的线条不太准确。 你经过这些景点的中心吗? 顶边? 底边? 顶部,另一个的底部? 显然,有许多不同的选择。 如果两个点彼此接近,那么变化将更大。

浮点数具有一定程度的不准确性,由于它们表示数字的方式,因此它们更多地与情况2相对应而不是情况1(人们可以建议相当于使用任意精度的librray)。 世界上没有任何算法可以弥补这一点。 不精确的数据,不精确的数据输出

检查x0和x1之间的距离是否小,即fabs(x1 - x0)<eps。 然后该线与坐标系的y轴平行,即您不能根据 x来计算该线的y值。 你有无数的y值,因此你必须以不同的方式对待这种情况。

如何计算如下:

t = sign * power2 ( sqrt (abs(x - x0))/ sqrt (abs(x1 - x0)))

这个想法是使用数学等价公式,其中低(x1-x0)影响较小。 (不确定我写的那个是否符合这个标准)

正如MSalters所说,问题已经存在于原始数据中。

插值/外推需要斜率,在给定条件下已经具有低精度(对于远离原点的极短线段最差)。

选择算法canot重新获得这种准确性损失。 我的直觉是,不同的评估顺序不会改变事物,因为错误是由减法引入的,而不是偏差。


理念:
如果在生成线条时有更准确的数据,则可以将表示从((x0,y0),(x1,y1))更改为(x0,y0,角度,长度)。 你可以存储角度或斜率,斜率有一个极点,但角度需要触发功能......丑陋。

当然,如果您经常需要终点,那么这将无效,并且您有很多行无法存储其他数据,我不知道。 但也许有另一种表现形式可以很好地满足您的需求。

在大多数情况下,双打有足够的分辨率,但这也会使工作集加倍。

暂无
暂无

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

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