是否有迭代方法来计算沿扫描线的半径?

时间:2009-09-14 21:04:15

标签: optimization mathematical-optimization

我正在处理一系列点,这些点都具有相同的Y值,但X值不同。我通过将X递增1来完成这些点。例如,我可能有Y = 50,X是-30到30之间的整数。我的算法的一部分包括从每个点找到到原点的距离,然后进行进一步处理。

在分析之后,我发现距离计算中的sqrt调用占用了我很大的时间。是否有迭代方法来计算距离?

换句话说:

我想有效地计算:r[n] = sqrt(x[n]*x[n] + y*y))。我可以保存上一次迭代的信息。每次迭代都会通过递增x来改变,因此x[n] = x[n-1] + 1。我不能使用sqrt或trig函数,因为它们太慢,除了在每条扫描线的开头。

我可以使用近似值,只要它们足够好(误差小于0.1%)并且引入的误差是平滑的(我不能将其归结为预先计算的近似表)。

其他信息: x和y总是-150到150之间的整数

我明天会尝试一些想法,并根据最快的答案标出最佳答案。

结果

我做了一些时间

  • 距离公式:16 ms /迭代
  • Pete的interperlating解决方案:8 ms / iteration
  • 争吵预计算解决方案:8ms / iteration

我希望测试能在两者之间做出决定,因为我喜欢这两个答案。我要和Pete一起去,因为它占用的内存较少。

6 个答案:

答案 0 :(得分:4)

为了感受它,对于你的范围y = 50,x = 0给出r = 50和y = 50,x = +/- 30给出r~ = 58.3。您希望近似值为+/- 0.1%,或+/- 0.05绝对值。这比大多数图书馆都要低很多。

两种近似方法 - 您根据先前值的插值计算r,或使用合适系列的几个项。

从先前的r

插值

r =(x 2 + y 2 1/2

dr / dx = 1/2。 2x。 (x 2 + y 2 -1/2 = x / r

    double r = 50;

    for ( int x = 0; x <= 30; ++x ) {

        double r_true = Math.sqrt ( 50*50 + x*x );

        System.out.printf ( "x: %d r_true: %f r_approx: %f error: %f%%\n", x, r, r_true, 100 * Math.abs ( r_true - r ) / r );

        r = r + ( x + 0.5 ) / r; 
    }

给出:

x: 0 r_true: 50.000000 r_approx: 50.000000 error: 0.000000%
x: 1 r_true: 50.010000 r_approx: 50.009999 error: 0.000002%
....
x: 29 r_true: 57.825065 r_approx: 57.801384 error: 0.040953%
x: 30 r_true: 58.335225 r_approx: 58.309519 error: 0.044065%

似乎符合0.1%误差的要求,所以我没有打扰下一个编码,因为它需要更多的计算步骤。

截断系列

对于x接近零的sqrt(1 + x)的泰勒系列是

sqrt(1 + x)= 1 + 1/2 x - 1/8 x 2 ... +( - 1/2) n + 1 x 名词

使用r = y sqrt(1 +(x / y) 2 )然后你要找一个项t =( - 1/2) n + 1 0.36 n ,其幅度小于0.001,log(0.002)> n log(0.18)或n> 3.6,所以将术语带到x ^ 4应该是好的。

答案 1 :(得分:2)

Y=10000
Y2=Y*Y
for x=0..Y2 do
  D[x]=sqrt(Y2+x*x)

norm(x,y)=
  if (y==0) x
  else if (x>y) norm(y,x) 
  else {
     s=Y/y
     D[round(x*s)]/s
  }

如果您的坐标是平滑的,那么可以使用线性插值扩展该想法。为了更精确,增加Y.

这个想法是s *(x,y)在y = Y的线上,你预先计算了它的距离。获取距离,然后除以s。

我认为你真的需要距离而不是它的正方形。

你也可以找到一个普通的sqrt实现,牺牲一些速度的准确性,但我很难想象打败FPU可以做什么。

通过线性插值,我的意思是将D[round(x)]更改为:

f=floor(x)
a=x-f
D[f]*(1-a)+D[f+1]*a

答案 2 :(得分:1)

这并不能真正回答你的问题,但可能有所帮助...

我要问的第一个问题是:

  • “我真的需要这个sqrt吗?”。
  • “如果没有,我怎样才能减少sqrts的数量?”
  • 然后你的:“我可以用巧妙的计算替换剩余的sqrts吗?”

所以我从:

开始
  • 您是否需要精确的半径,或半径平方是否可以接受?快速近似于sqrt,但可能不够准确,无法满足您的规格。
  • 您可以使用镜像象限或八分之一处理图像吗?通过批量处理相同半径值的所有像素,可以将计算次数减少8倍。
  • 你能预先计算半径值吗?您只需要一个表,该表是您正在处理的图像大小的四分之一(或可能是第八个),并且该表只需要预先计算一次,然后重新用于算法的多次运行。

如此聪明的数学可能不是最快的解决方案。

答案 3 :(得分:1)

那么总是会尝试优化你的sqrt,我见过的最快的就是旧的carmack地震3 sqrt:

http://betterexplained.com/articles/understanding-quakes-fast-inverse-square-root/

那就是说,因为sqrt是非线性的,所以你不可能沿着你的线进行简单的线性插值来得到你的结果。最好的想法是使用表查找,因为这将使您能够快速访问数据。而且,由于您似乎是按整数进行迭代,因此表查找应该非常准确。

答案 4 :(得分:0)

好吧,你可以围绕x = 0镜像开始(你只需要计算n&gt; = 0,并且那些结果会导致相应的n <0)。在那之后,我将看一下使用sqrt上的导数(a ^ 2 + b ^ 2)(或相应的sin)来利用常数dx。

如果这还不够准确,我是否可以指出这对于SIMD来说是一个相当不错的工作,它将在SSE和VMX(以及着色器模型2)上为您提供倒数平方根操作。

答案 5 :(得分:0)

这与HAKMEM item

有关
  

第149项(明斯基):循环算法   这是一种优雅的绘画方式   点绘图显示中的圆圈:

NEW X = OLD X - epsilon * OLD Y
NEW Y = OLD Y + epsilon * NEW(!) X
     

这是一个非常圆的椭圆   以其大小为中心的原点   由初始点确定。   epsilon确定角度   循环点的速度,和   略微影响偏心率。如果   epsilon是2的幂,那么我们就没有   甚至需要乘法,更不用说了   平方根,正弦和余弦!该   “圆圈”将非常稳定   因为分数很快就会变成   周期性的。

     

圆算法是由发明的   我试图保存一个错误   注册显示黑客!本·格利   有一个惊人的显示黑客只使用   约六或七条指令,和   这是一个伟大的奇迹。但事实确实如此   基本上是面向行的。发生了   对我来说,这将是令人兴奋的   有曲线,我试图得到一个   曲线显示黑客与最小   指令。