Математически говоря, нулевой вектор не может быть нормализован. Его длина будет всегда оставаться 0
.
Для данного вектора v = (v1, v2, ..., vn)
мы имеем: ||v|| = sqrt(v1^2 + v2^2 + ... + vn^2)
. Давайте помнить, что нормализованный вектор является тем, который имеет ||v||=1
.
Таким образом для v = 0
мы имеем: ||0|| = sqrt(0^2 + 0^2 + ... + 0^2) = 0
. Вы никогда не можете нормализовать это.
Также важный, чтобы отметить, что для обеспечения непротиворечивости Вы не должны возвращаться NaN
или любое другое нулевое значение. Нормализованная форма v=0
действительно v=0
.
Это даже хуже, чем предлагает Ювал.
Математически, для заданного вектора x вы ищете новый вектор x / || x ||
где ||. || это норма, о которой вы, вероятно, думаете, как евклидова норма с
||. || = sqrt (dot (v, v)) = sqrt (sum_i x_i ** 2)
Это числа с плавающей запятой, поэтому недостаточно просто защититься от деления на ноль, у вас также есть проблема с плавающей запятой если x_i все маленькие (они могут снизиться, и вы потеряете величину).
По сути, все сводится к тому, что если вам действительно нужно уметь правильно обрабатывать небольшие векторы, вам придется проделать еще некоторую работу.
Если малые и нулевые векторы не имеют смысла в вашем приложении, вы можете проверить величину вектора и сделать что-то подходящее.
(обратите внимание, что как только вы начинаете работать с числами с плавающей запятой, а не с действительными числами, делать такие вещи, как возведение в квадрат, а затем квадратные числа с корнем (или их суммы) становится проблематичным как для больших, так и для малых концов представимого диапазона )
Итог: правильно выполнять числовую работу во всех случаях сложнее, чем кажется на первый взгляд.
Например, вне моей головы потенциальные проблемы с этой (нормализацией) операцией, выполненной наивным способом
Как уже упоминалось несколько раз, вы не можете нормализовать нулевой вектор. Итак, ваши варианты:
Вариант 4 не очень хорош, потому что некоторые языки (такие как C) не имеют исключений, и нормализация вектора обычно находится в очень низком код уровня. Создание исключения довольно дорого, и любой код, который может захотеть обработать случай с нулевым / малым вектором, получит ненужный удар по производительности, когда это произойдет.
У варианта 1 есть проблема, заключающаяся в том, что возвращаемое значение не будет иметь единичную длину, и поэтому оно может молча вносить ошибки в код вызова, который предполагает, что результирующий вектор имеет единичную длину.
Вариант 2 имеет проблему, аналогичную варианту 1, но, поскольку NaN обычно намного более заметны, чем нули, он, вероятно, будет проявляться легче.
Я думаю, что вариант 3 является лучшим решением, хотя он и делает интерфейс более сложным. Вместо того чтобы сказать
vec3 = myVec.normalize();
Теперь вы должны сказать что-то вроде
vec3 result;
bool success = myVec.normalize(&result);
if(success)
// vector was normalized
else
// vector was zero (or small)
Очень похоже на 0/0. Должен выбросить исключение или вернуть NaN.
Нулевой вектор уже нормализован, при любом определении нормы вектора, с которым я когда-либо сталкивался, так что это один случай, который рассматривается.
Что касается вектора с компонентами, сумма которых равна нулю - это зависит от того, какое определение нормы вы используете. С простой старой L2-нормой (евклидовым расстоянием между началом и вектором) стандартная формула для вычисления нормализованного вектора должна работать нормально, поскольку она сначала возводит в квадрат отдельные компоненты.
(0,0,0) следует нормализовать (0,0,0) плюс предупреждение (или исключение).
математически это не определено, я думаю.
Ну, вам придется делить на ноль, что вы не можете сделать, поэтому я думаю, что большинство языков будет иметь какое-то значение NaN.
Ссылки:
Учитывая вектор v, нормализовать его означает сохранить его направление и сделать его единичной длиной, умножив его на хорошо выбранный коэффициент.
Это очевидно невозможно для нулевого вектора, потому что он на самом деле не имеет направления или потому что его длина не может быть изменена путем умножения его на некоторый коэффициент (он всегда будет равен нулю).
Я хотел бы предложить, чтобы любая процедура, для которой вы хотели бы использовать свой вектор, и для которой требуется нормализация этого вектора, не является четко определенной для нулевых векторов.
Все зависит от того, как вы определяете «нормализовать». Одно из возможных расширений этого термина состоит в том, чтобы сказать, что результатом этой операции является любой вектор длины единицы (здесь я в основном использую (1, 0, 0)). Это полезно, например, когда вам нужна нормализация для возврата направления к границе круга из заданной точки.
С математической точки зрения нулевой вектор не может быть нормализован. Это пример того, что мы называем в вычислительной геометрии «вырожденным случаем», и это огромная тема, доставляющая много головной боли разработчикам геометрических алгоритмов. Я могу представить себе следующие подходы к проблеме.
degenerate_case_exception
. is_degenerate_case
выходной параметр для вашей процедуры. Лично я в своем коде везде использую подход 3. Одним из его преимуществ является то, что он не позволяет программисту забыть о вырожденных случаях.
Обратите внимание, что из-за ограниченного диапазона чисел с плавающей запятой, даже если входной вектор не равен нулевому вектору, вы все еще может получить бесконечные координаты в выходном векторе. Из-за этого я не считаю 1. такой подход - плохое дизайнерское решение.
Я могу вам порекомендовать избегать использования решения, вызывающего исключение. Если вырожденные случаи среди других редки, то генерация исключения не замедлит работу программы. Но проблема в том, что в большинстве случаев вы не можете знать, что дегенеративные случаи будут редкими.