Преобразование из градусов в радианы недостаточно точное

Я очень хорошо знаю, как работает преобразование градусов в радианы, но у меня все еще есть проблема с этим. Проблема в его точности.

Как вы, вероятно, знаете тригонометрические функции из System.Math библиотека использует радианы, но мне нужны были тригонометрические функции, в которые вы вставляете градусы. Итак, я создал Helper класс с Cos,Sin а также Tan функции, которые будут просто конвертировать градусы в радианы внутри них и вызывать их Math функции.

public static class Helper
{
    public static double Sin(double degrees) => Math.Sin(degrees * Math.PI / 180.0);
    public static double Cos(double degrees) => Math.Cos(degrees * Math.PI / 180.0);
    public static double Tan(double degrees) => Math.Tan(degrees * Math.PI / 180.0);
}

Но, например, когда я использую Math.Tan(45) он возвращает 0.999999999989 вместо одного. Мой вопрос к вам, мои друзья, как сделать это абсолютно точно.

Заранее спасибо.

РЕДАКТИРОВАТЬ: я понимаю, будут небольшие ошибки, и от них невозможно избавиться. Что я точно хочу знать, так это как округлить значение до необходимой суммы (не слишком много, не слишком мало), чтобы оно работало в любом случае.

EDIT2: контекст очень важен в этом вопросе, так что вот оно:

Мне нужна тригонометрия для моего Line класс, который имеет следующий конструктор:

public Line(Vector2 beginning, Vector2 end)
{
    this.beginning = beginning;
    this.end = end;
    this.direction = 180.0 * Math.Atan(end.Y - beginning.Y / end.X - beginning.X) / Math.PI;
    this.slope = Helper.Tan(direction);
    this.length = Helper.DistanceBetweenTwoPoints(beginning, end);
    this.offset = beginning.X;
 }

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

Раскрытие: Vector2 это структура с double x а также double y, Это все.

2 ответа

Вы всегда будете иметь ошибки округления при использовании двойных чисел. Это сама природа арифметики с плавающей точкой. Double может содержать приблизительно 16 десятичных цифр. И на каждой операции ошибка складывается. "Хитрость" заключается в том, чтобы принять эту ошибку, никогда не сравнивать удваивающие / плавающие числа с равенством (если вы не знаете, что делаете) и делать значимые округления на выходе.

Смотрите http://floating-point-gui.de/.

Чтобы решить, лежит ли точка на линии между двумя точками или похожими проблемами, вам не нужно округлять, а использовать допуск: вычислите кратчайшее расстояние между точкой и линией и проверьте, находится ли она ниже порогового значения. Насколько большим может быть этот порог, зависит от вашего приложения, и нет простого ответа. Если, например, ваши очки находятся в диапазоне 0,10, для меня будет разумным допуск 1e-7. Если вы хотите общую реализацию, вы должны взять максимум

  • расстояние между точками, умноженное на коэффициент (например, dist(begin, end) * 1e-8)
  • абсолютное значение для очень малых расстояний (например, 1e-10)

Но вы все равно можете столкнуться с проблемами. Например, если ваша линия идет от (10000000.1,0) в (10000000.2,0)смотрите здесь для деталей. Это широкая тема в целом.

Используя наклон, который вы получаете через Helper.Tan(direction) рассчитать, находится ли точка на линии, для разных уклонов будет различаться количество ошибок, которые ухудшаются с приближением направления +/- PI / 2 так как tan(x) приближается к бесконечности как x подходы +/- PI/2 и в +/- PI/2 это не будет работать вообще.

Чтобы увидеть, находится ли точка около линии, используйте перекрестное произведение вектора линии и вектора на точку. Например, линия от точки A в B и дело в том, P затем Cross(B-A,P-A) будет ноль, когда P на линии Для борьбы с ошибкой, которая связана с длиной линии, разделите перекрестное произведение на длину линии в квадрате и сравните с произвольным небольшим допуском epsilon,

// A,B, P are Vector2
double cross = (B.x-A.x) * (P.y-A.y) - (B.y-A.y) * (P.x-A.x);
cross /= (B.x-A.x) * (B.x-A.x) + (B.y-A.y) * (B.y-A.y);
double epsilon = 1.0e-6; // 1 millionths of a unit
if (Math.abs(cross) < epsilon) {
     // point P is on line A,B
} 

КСТАТИ Math.PI является приближением иррационального числа, его использование всегда будет вводить ошибку, независимо от того, какую систему вы используете. ИП просто нельзя записать в виде числа. В реальном мире все измерения имеют ошибку, и для всех практических применений цифры PI 7 3.141592 более чем достаточно точны.

Другие вопросы по тегам