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

时间:2009-05-22 07:58:22

标签: c++ c optimization math numerical

我正在计算给定横坐标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总是很大。

8 个答案:

答案 0 :(得分:3)

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

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

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

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

答案 1 :(得分:2)

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

答案 2 :(得分:1)

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

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

// 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;

答案 3 :(得分:1)

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

我使用双精度计算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()函数来过滤掉伪造的值。我怀疑这些结果来自于差异或分裂的下溢。

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

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

答案 4 :(得分:0)

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

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

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

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

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

答案 5 :(得分:0)

检查x0和x1之间的距离是否小,即fabs(x1-x0)&lt; EPS。然后该线与坐标系的y轴平行,即您无法在x上对该线的依赖的y值进行计算。你有无数的y值,因此你必须以不同的方式对待这种情况。

答案 6 :(得分:0)

如何计算:

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

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

答案 7 :(得分:0)

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

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

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


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

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

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