C++ 使用浮动进行最精确的直线交点纵坐标计算?

C++ 使用浮动进行最精确的直线交点纵坐标计算?,c++,c,optimization,math,numerical,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的方法 i、 我应该先计算(x-x0)*

我在计算一条直线上某点在给定横坐标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的方法

i、 我应该先计算(x-x0)*(y1-y0),然后再除以(x1-x0)吗


y1-y0之间的差异总是很大的。

如果有可能,可以在计算中引入两种情况,具体取决于abs(x1-x0) 编辑。另一种可能是使用二分法搜索的变体逐点获得结果。这将较慢,但在极端情况下可能会改善结果

// 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;
//输入是X
xmin=min(x0,x1);
xmax=max(x0,x1);
ymin=min(y0,y1);
ymax=最大值(y0,y1);

对于(inti=0;i,如果源数据已经是一个浮点值,那么就已经存在基本的不精确性

为了进一步解释,想象一下如果你以图形的方式来做这件事,你有一张二维的图表纸,上面有两个点

案例1:这些点非常精确,并用非常锋利的铅笔标记。很容易画出连接它们的线,然后在给定x的情况下很容易得到y(反之亦然)

案例2:这些点用一支大而粗的毛毡尖笔标记,就像宾果标记一样。很明显,你画的线不太准确。你穿过这些点的中心吗?上边缘?下边缘?一个点的顶部,另一个点的底部?显然有许多不同的选择。如果两个点彼此靠近,那么变化会发生我会更伟大


由于浮点数表示数字的方式,浮点数本身具有一定程度的不精确性,因此浮点数与情况2的对应程度大于情况1(有人可能认为这相当于使用任意精度的librray)世界上没有任何算法可以弥补这一点。不精确的数据输入,不精确的数据输出检查x0和x1之间的距离是否很小,即fabs(x1-x0)在很大程度上,你的根本问题是根本的。当(x1-x0)是小的,这意味着在x1和x0的尾数中只有几个不同的位。并且,通过扩展,x0和x1之间只有有限的浮点数。例如,如果尾数中只有较低的4位不同,则它们之间最多有14个值

在您的最佳算法中,
t
项表示这些低位。继续或举例来说,如果x0和x1相差4位,那么t也只能取16个值。这些可能值的计算相当稳健。无论您是在计算3E0/14E0还是3E-12/14E-12,结果都将接近数学值e 2014年3月3日


您的公式还有一个额外的优点,就是y0计算如下:

t = sign * power2 ( sqrt (abs(x - x0))/ sqrt (abs(x1 - x0)))
其想法是使用一个数学等效公式,其中low(x1-x0)的影响较小。
(不确定我写的是否符合这个标准)

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

我使用双精度计算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;
} 
我计算了双精度结果和单精度结果之间差异的平均值和STDEV

结果是,平均1000和10K以上的随机值集都没有。我使用了icc编译器,包括优化和未优化以及g++

请注意,我必须使用isnan()函数来过滤虚假的值。我怀疑这些值是由差分或除法中的下溢造成的

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


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

您可以看看Qt的“QLine”(如果我记得正确的话)源;他们实现了一个从一个“Graphics Gems”书籍(参考资料必须在代码注释中,这本书是几年前在EDonkey上出版的),这本书反过来又保证了当以给定的位宽度执行计算时,对给定屏幕分辨率的适用性(如果我没有错的话,他们使用定点算术).

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

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

我的直觉是,不同的评估顺序不会改变事情,因为误差是由减法引入的,而不是由除法引入的


想法:
如果生成线时有更精确的数据,可以将表示形式从((x0,y0),(x1,y1))更改为(x0,y0,角度,长度)。可以存储角度或坡度,坡度有极点,但角度需要trig函数…丑陋

当然,如果你经常需要终点,而且你有太多的线,你不能