точность плавающей запятой в haskell - PullRequest
2 голосов
/ 26 августа 2010

Я написал небольшую программу для определения положения точки (x, y) относительно линии, определяемой точкой (px, py) и углом (градус) к оси x (в декартовой координате). система).

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

Это работает исключительно хорошо для точек, которые находятся далеко от линии, но не очень хорошо для точек, которые находятся близко или на линии. Как я могу улучшить точность ??

Ответы [ 5 ]

6 голосов
/ 26 августа 2010

загар (90 °) не определен, поэтому повышенная точность не поможет вам в этом.Из-за природы чисел с плавающей запятой (которые округлены и которые не могут точно представлять π / 2), вызов tan(deg * pi / 180) даст либо очень большое, либо очень маленькое число, в зависимости от того, с какой стороны от π / 2 значениеокруглено.

Точный результат будет NaN, но это также не сильно вам поможет.Вам придется обрабатывать эти проблемные случаи отдельно или использовать другой алгоритм, который не имеет такого рода «исключительных» случаев.

3 голосов
/ 26 августа 2010

Вы можете использовать тот факт, что ваша строка, заданная angle (px,py), может быть задана эквациональным определением в форме a*x+b*y+c=0, где a,b,c дано как

sin angle*(x-px) - cos angle*(y-py) = 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" означают по отношению к общей строке.

1 голос
/ 26 августа 2010

Как у вас работает этот код?Ваш метод вернет только то, что точка находится на прямой, если она абсолютно точная на линии.Если вы рисуете линию на экране и читаете, где пользователь щелкает, тогда, если ваш угол не является «хорошим» углом, например 0, 90 и т. Д., Маловероятно, чтобы пиксель лежал точно на линии.

Сначаларассмотрим рисование линии точно при 0 градусах, начиная с (100, 100).Нажатие на (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. Но этот крошечный бит нарушает ваше равенство, и ваша функция скажет:Справа ".

Возможно, вы хотите сравнить расстояние до линии со значением эпсилона (см., Например, первую часть этой страницы: http://www.cygnus -software.com /apers / comparingfloats /comparingfloats.htm - но не хакерский бит позже), чтобы допустить небольшую терпимость к тому, чтобы быть так близко к линии, которую вы считаете на линии.

1 голос
/ 26 августа 2010

Проблема в том, что при нескольких вызовах таких функций, как tan или pi, по умолчанию ваши вычисления набирают тип Double или вообще любой тип Floating.В среде программистов общеизвестно, что вы должны никогда сравнивать два значения с плавающей запятой на равенство (вы делаете это неявно), потому что вычисления могут часто вызывать небольшие ошибки.Было бы лучше определить собственную функцию, которая проверяет, находится ли разница двух плавающих чисел под определенным пределом:

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

И сначала обработать случаи «равенства».

0 голосов
/ 23 апреля 2011

Как стиль, ваш код будет более элегантно написан с использованием case: data

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
...