Преобразование из градусов в радианы недостаточно точное
Я очень хорошо знаю, как работает преобразование градусов в радианы, но у меня все еще есть проблема с этим. Проблема в его точности.
Как вы, вероятно, знаете тригонометрические функции из 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 более чем достаточно точны.