Для того чтобы сравнить два вектора – определить, указывают ли оба вектора в одну и ту же точку, нужно сравнить координаты обоих векторов и если они соответственно равны, значит и вектора тоже равны. Однако координаты векторов задаются величинами типа float, сравнение которых на равенство, из-за особенностей хранения такого типа в памяти компьютера, никогда не бывает точным.
Рассмотрим это на примере. Добавим в сцену два объекта, состоящие каждый из одного ребра. Это ребро и будет обозначать вектор в пространстве 3D сцены. Назовем объекты V0 и V1 и расположим их в сцене совершенно одинаково – из начала сцены (0, 0, 0) в любую другую точку. Проще всего создать сначала один объект, а потом сделать его копию, нажав shift + d.
Теперь мы можем получить координаты векторов, образованных нашими объектами:
1 2 3 4 5 6 7 8 |
v0 = bpy.data.objects['V0'].data.vertices[1].co v1 = bpy.data.objects['V1'].data.vertices[1].co print('v0 x:', v0.x, 'y: ', v0.y, 'z: ', v0.z) print('v1 x:', v1.x, 'y: ', v1.y, 'z: ', v1.z) # v0 x: 1.6818300485610962 y: -0.2943689227104187 z: 3.1925301551818848 # v1 x: 1.6818300485610962 y: -0.2943689227104187 z: 3.1925301551818848 |
Как мы видим, координаты векторов полностью совпадают.
Глядя на это, кажется что мы можем написать элементарную функцию, которая будет сравнивать соответствующие координаты двух векторов и возвращать результат сравнения по всем трем координатам.
Что-то вроде этого:
1 2 3 4 |
def vector_equal(v0, v1): if v0.x == v1.x and v0.y == v1.y and v0.z == v1.z: return True return False |
И если мы попробуем протестировать эту функцию на наших двух векторах, мы получим правильный ответ:
1 2 3 |
print('are vectors equal:', vector_equal(v0, v1)) # are vectors equal: True |
Однако не будем спешить радоваться.
Из курса математики мы знаем, что значение не измениться, если произвести над ним какое-либо математическое действие, а потом произвести обратное действие. Например, прибавить единицу, а потом отнять единицу. Или поделить на десять, а потом умножить на десять.
Давайте в качестве эксперимента поделим координату X нашего вектора V1 на три, а потом обратно умножим ее на три.
1 2 |
v1.x /= 3 v1.x *= 3 |
Итоговое значение не должно отличаться от исходного, однако если мы напечатаем координаты векторов снова, мы увидим некоторые изменения.
1 2 3 4 5 |
print('v0 x:', v0.x, 'y: ', v0.y, 'z: ', v0.z) print('v1 x:', v1.x, 'y: ', v1.y, 'z: ', v1.z) # v0 x: 1.6818300485610962 y: -0.2943689227104187 z: 3.1925301551818848 # v1 x: 1.6818299293518066 y: -0.2943689227104187 z: 3.1925301551818848 |
Начиная примерно с пятого знака после запятой, значение координаты X теперь отличается от того же значения во втором векторе, которое мы не трогали.
Противоречий с математикой здесь нет, просто нужно помнить, что компьютерная память устроена так, что не может хранить числа с плавающей точкой с абсолютной точностью.
И теперь, если мы вновь попробуем сравнить вектора написанной нами выше функцией, мы получим противоположный результат.
1 2 3 |
print('are vectors equal:', vector_equal(v0, v1)) # are vectors equal: False |
Как же тогда нам сравнивать вектора?
Для того чтобы при сравнении чисел с плавающей точкой получать более предсказуемые результаты, обычно их не сравнивают на абсолютное равенство, а проверяют, отличается ли одно число от другого больше чем на заданную дельту – обычно маленькое число с тремя-пятью нулями после запятой.
Самая простая формула для такого сравнения выглядит так:
1 |
abs(число 1 - число 2) < delta |
и ее более применимый аналог:
1 |
abs(число 1 - число 2) < delta * max(abs(число 1), abs(число 2)) |
Такой вариант позволяет удобно разводить дельту сравнения, если сравниваются два очень больших числа. Вместе с тем, такая формула плохо работает, если сравниваются два числа близких к нулю.
Поэтому чаще всего используется вариант в котором учитываются две дельты: относительная rel_tol и абсолютная abs_tol. В этом случае формула выглядит следующим образом:
1 |
abs(число 1 - число 2) < max(rel_tol * max(abs(число 1), abs(число 2)), abs_tol) |
Вернемся к нашим векторам и попробуем применить данную формулу в функции их сравнения.
1 2 3 4 5 6 |
def vector_close(v0, v1, rel_tol=1e-09, abs_tol=0.001): if abs(v0.x - v1.x) < max(rel_tol * max(abs(v0.x), abs(v1.x)), abs_tol) \ and abs(v0.y - v1.y) < max(rel_tol * max(abs(v0.y), abs(v1.y)), abs_tol) \ and abs(v0.z - v1.z) < max(rel_tol * max(abs(v0.z), abs(v1.z)), abs_tol): return True return False |
Применим ее к нашим векторам с различной дельтой.
1 2 |
print('are vectors close: ', vector_close(v0, v1, abs_tol=0.0000001)) print('are vectors close: ', vector_close(v0, v1, abs_tol=0.00001)) |
Пока наши вектора абсолютно идентичны, мы получаем следующий результат:
1 2 |
# are vectors close: True # are vectors close: True |
А после появления погрешности, вызванной математическим действиями:
1 2 |
# are vectors close: False # are vectors close: True |
То есть, сравнивая два вектора с указываемой дельтой, мы можем считать, что вектора одинаковы в пределах заданной погрешности.