2010-08-26 125 views
2

我写了一个小程序来查找点(x,y)相对于由点(px,py)定义的直线和与x轴(角度)在笛卡尔坐标系中)。haskell浮点精度

toRadian deg = deg * (pi/180) 

lineSlope deg = tan $ toRadian deg 

lineYintercept (x,y) deg = y - (x * lineSlope deg) 

relativePointPosition (px,py) deg (x,y) 
    | s < 0 && deg>=0 && deg<90 = "Left" 
    | s < 0 && deg>=90 && deg<180 = "Left" 
    | s < 0 && deg>=180 && deg<270 = "Right" 
    | s < 0 && deg>=270 && deg<360 = "Right" 
    | s > 0 && deg>=0 && deg<90 = "Right" 
    | s > 0 && deg>=90 && deg<180 = "Right" 
    | s > 0 && deg>=180 && deg<270 = "Left" 
    | s > 0 && deg>=270 && deg<360 = "Left" 
    | s > 0 && deg==360 = "Right" 
    | s < 0 && deg==360 = "Left" 
    | otherwise = "On the line" 
    where s = lineSlope deg * x + lineYintercept (px,py) deg - y 

它的工作原理以及exceptionaly对于那些从行远点,但不是这么好是接近或点就行。 我该如何提高准确度?

+0

总是有任意精度[科学](https://hackage.haskell.org/package/科学)号码。 – 2016-02-01 08:12:44

回答

1

问题是,对tanpi等函数的多个调用默认计算为输入Double,或者通常输入任何类型的Floating。它在程序员中是众所周知的,你应该从来没有比较两个浮点值相等(你确实隐含),因为计算可能经常会导致小错误。这将是更好地定义自己的函数,它测试两个浮点数的差是否在一个特定的限制:

equals :: Floating a => a -> a -> Bool 
equals a b = abs (a - b) < 0.000001 -- change the value to whatever fits best 

而且首先处理“平等”的情况。

6

棕褐色(90°)未定义,所以提高的准确性不会真正帮助您。由于浮点数字的性质(这是四舍五入的,并且不能完全表示π/2),调用tan(deg * pi/180)将给出非常大或非常小的数字,具体取决于值的四舍五入。

确切的结果是NaN,但这也没有什么帮助。你将不得不分别处理这些有问题的情况,或者使用不具有这种“例外”情况的不同算法。

+0

这就是更数学的一面。 – fuz 2010-08-26 14:22:09

+3

呃,是吗?如果数学不起作用,你的程序也不会去。 – jrockway 2010-08-30 06:09:21

1

你是怎么运行这段代码的?你的方法只会返回该点在线上,如果是绝对位于线上。如果您在屏幕上绘制线条并阅读用户点击的位置,除非您的角度是像0,90等那样的“好”角度,否则像素不太可能位于线条上。

首先考虑从(100,100)开始在正好0度处画一条线。点击(200,100)将在线上,因为tan 0 == 0,所以lineSlope 0 == 0,因此s下降到100 - 100 == 0.但现在考虑线是否在0.000001度。根据我的计算器,得到一个类似于0.000000017的数字。所以现在点击(200,100)评估s为0.000000017 * 200 + 100 - 100 * 0.000000017 - 100,这些都在0以上的微小点上出现。但是这样一点点打破了你的平等比较,你的函数会说“对”。

您可能想要将距离与具有epsilon值的行进行比较(例如,请参阅此页面的第一部分:http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm - 但稍后请勿使用hacky位),以便允许稍微容许这么近到你算作的线上。

3

你可以使用一个事实,即你的线,angle (px,py)给出可以由等式定义,其中a,b,c

sin angle*(x-px) - cos angle*(y-py) = 0 

因此给出的形式a*x+b*y+c=0上给出的,你可以简单地定义

lineF angle (px,py) = \ (x,y) -> (sin angle)*(x-px)-(cos angle)*(y-py) 

,并使用这个在测试这样

lineTest angle (px,py) (x,y) 
    | f (x,y) < -eps = "Left" 
    | f (x,y) > eps = "Right" 
    | otherwise = "On the line" 
    where 
    f = lineF angle (px,py) 
    eps = 1e-9 -- confidence interval as Neil Brown specified. 

基本上,以最通用的形式定义一条线的函数为您提供了一个函数,该函数将计算距线的距离,您可以使用该函数来测试事物的位置。您可能需要一些小窍门来弄清楚"Left""Right"实际上是否应该表示通用线。

0

就像一个风格问题,您的代码将被更优雅的使用case写着:数据

data Position = L 
       | R 
       | OnTheLine 
       deriving Show 

relativePointPosition (px,py) deg (x,y) 
    | s < 0 
    = case() of { 
     _ | deg >= 0 && deg < 90 -> L 
      | deg >= 90 && deg < 180 -> L 
      | deg >= 180 && deg < 270 -> R 
      | deg >= 270 && deg < 360 -> R 
      | deg == 360    -> L 

    } 
    | otherwise -- s > 0 
    = case() of { 
     _ | deg >= 0 && deg < 90 -> R 
      | deg >= 90 && deg < 180 -> R 
      | deg >= 180 && deg < 270 -> L 
      | deg >= 270 && deg < 360 -> L 
      | deg == 360    -> R 
      | otherwise     -> OnTheLine 
    } 
    where 
    s = lineSlope deg * x + lineYintercept (px,py) deg - y