Как нормализовать нулевой вектор
Предположим, у вас есть функция "нормализации", которая принимает список чисел (представляющих вектор) в качестве входных данных и возвращает нормализованный вектор. Каким должен быть результат, когда вектор равен всем нулям или сумма его компонентов равна нулю?
10 ответов
математически говоря, нулевой вектор не может быть нормализован. Его длина навсегда останется 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
или любое другое значение null. Нормализованная форма v=0
действительно v=0
.
Это даже хуже, чем предполагает Юваль.
математически, учитывая вектор x, вы ищете новый вектор x|| / x//
где ||./ / это норма, о которой вы, вероятно, думаете как о евклидовой норме с
||.| / = sqrt (точка (v, v)) = sqrt(sum_i x_i**2)
Это числа с плавающей запятой, поэтому недостаточно просто защитить от деления на ноль, у вас также есть проблема с плавающей запятой, если все x_i малы (они могут underflow и вы теряете величину).
в основном все сводится к тому, что если вам действительно нужно правильно обрабатывать небольшие векторы, вам придется сделать еще немного работы.
Если малые и нулевые векторы не имеют смысла в вашем приложении, вы можете проверить величину вектора и сделать что-то соответствующее.
(обратите внимание, что как только вы начнете иметь дело с плавающей точкой, а не с реальными числами, делайте такие вещи, как квадрат, а затем квадрат укоренение чисел (или их сумм) проблематично как на больших, так и на малых концах представимого диапазона)
итог: правильная численная работа во всех случаях сложнее, чем кажется на первый взгляд.
например, с верхней части моей головы потенциальные проблемы с этой (нормализацией) операцией, выполненной наивным способом
- все компоненты (x_i) слишком малы
- любой одиночный компонент слишком большой (над квадратным корнем максимального representable) вернет бесконечность. Это сокращает доступные значения по компонентам на sqrt .
- если отношение большого компонента к малому компоненту слишком велико, вы можете эффективно потерять направление малых компонентов, если вы не будете осторожны
- etc.
математически говоря, нулевой вектор не может быть нормализован. Это пример того, что мы называем в вычислительной геометрии "вырожденным случаем", и это огромная тема, делающая большую головную боль для разработчиков алгоритмов геометрии. Я могу представить себе следующие подходы к проблеме.
- вы не делаете ничего особенного в случае нулевого вектора. Если ваш векторный тип имеет координаты с плавающей запятой, то в результате вы получите нулевые или бесконечные координаты (из-за деления на ноль).
- бросить
degenerate_case_exception
. - вы вводите логическое
is_degenerate_case
выходной параметр процедуры.
лично я в своем коде использую 3 подхода везде. Одним из его преимуществ является то, что он не позволяет программисту забывать иметь дело с вырожденными случаями.
обратите внимание, что из-за ограниченного диапазона чисел с плавающей запятой, даже если входной вектор не равен нулевому вектору, вы все равно можете получить бесконечное координаты в выходном векторе. Из-за этого я не рассматриваю 1. подход должен быть плохим дизайнерским решением.
то, что я могу порекомендовать вам, - это избежать решения для исключения. Если вырожденные случаи редки среди других, то выбрасывание исключений не замедлит работу программы. Но проблема в том, что в большинстве случаев вы не можете знать, что дегенеративные случаи будут редки.
Как уже упоминалось несколько раз, вы не можете нормализовать нулевой вектор. Итак, ваши варианты:
- вернуть нулевой вектор
- Вернуть NaN
- верните бит, указывающий, был ли вектор успешно нормализован, в дополнение к результату, если успешно
- исключение
Вариант 4 не очень хорош, потому что некоторые языки (например, C) не имеют исключений, и нормализация вектора обычно найдено в очень низкоуровневом коде. Выбрасывание исключения довольно дорого, и любой код, который может захотеть обработать нулевой/малый векторный случай, получит ненужный удар по производительности, когда это произойдет.
Вариант 1 имеет проблему, что возвращаемое значение не будет иметь длину единицы, и поэтому он может молча вводить ошибки в вызывающий код, который предполагает, что результирующий вектор имеет длину единицы.
Вариант 2 имеет аналогичную проблему с вариантом 1, но потому, что NaNs обычно гораздо заметнее, чем нули, это, скорее всего, проявится легче.
Я думаю, что Вариант 3 будет лучшим решением, хотя это делает интерфейс более сложен. Вместо того чтобы сказать
vec3 = myVec.normalize();
теперь вы должны сказать что-то вроде
vec3 result;
bool success = myVec.normalize(&result);
if(success)
// vector was normalized
else
// vector was zero (or small)
нулевой вектор уже нормализован, при любом определении нормы вектора, с которым я когда-либо сталкивался, так что это один случай.
Что касается вектора с компонентами, сумма которых равна нулю-это зависит от определения нормы, которую вы используете. С простой старой L2-нормой (евклидовым расстоянием между началом координат и вектором) стандартная формула для вычисления нормализованного вектора должна работать нормально, так как она сначала квадратирует отдельные компоненты.
(0,0,0) должно быть (0,0,0) нормализовано плюс предупреждение (или исключение), возможно.
математически это не определено, я думаю.
Ну, вам придется делить на ноль, чего вы не можете сделать, поэтому я думаю, что большинство языков будут иметь какое-то значение NaN.
ссылки:
учитывая вектор v, нормализовать его означает сохранить его направление и сделать его единичной длины, умножив его на хорошо выбранный коэффициент.
Это явно невозможно для нулевого вектора, потому что он действительно не имеет направления или потому, что его длина не может быть изменена путем мутлирования его некоторым фактором (он всегда будет равен нулю).
Я бы предложил, чтобы любая процедура, для которой вы хотели бы использовать свой вектор, и это требует, чтобы этот вектор был нормализовано, не определено для нулевых векторов.
все зависит от того, как вы определяете "нормализовать". Одним из возможных расширений термина является то, что результатом этой операции является любой вектор единичной длины (я в основном использую (1, 0, 0) здесь). Это полезно, например, когда требуется нормализация для возврата направления к границе круга из заданной точки.