Два способа нормализовать объект Vector3; вызывая Vector3.Normalize (), а другой - нормализуя с нуля:
class Tester {
static Vector3 NormalizeVector(Vector3 v)
{
float l = v.Length();
return new Vector3(v.X / l, v.Y / l, v.Z / l);
}
public static void Main(string[] args)
{
Vector3 v = new Vector3(0.0f, 0.0f, 7.0f);
Vector3 v2 = NormalizeVector(v);
Debug.WriteLine(v2.ToString());
v.Normalize();
Debug.WriteLine(v.ToString());
}
}
Приведенный выше код дает следующее:
X: 0
Y: 0
Z: 1
X: 0
Y: 0
Z: 0.9999999
Почему?
(Бонусные баллы: Почему я?)
Я уже предоставил лучшую версию. Прочитай заново.
Один из моих коллег указал, что флаг D3DCREATE_FPU_PRESERVE может исправить это поведение. Подробнее: goo.gl/YTiEB





Плевать на это. При использовании поплавков всегда есть какая-то ошибка. Если вам интересно, попробуйте переключиться на удвоение и посмотрите, происходит ли это по-прежнему.
У вас есть интересное обсуждение о строковом форматировании чисел с плавающей запятой.
Просто для справки:
Your number requires 24 bits to be represented, which means that you are using up the whole mantissa of a float (23bits + 1 implied bit).
Single.ToString () is ultimately implemented by a native function, so I cannot tell for sure what is going on, but my guess is that it uses the last digit to round the whole mantissa.
The reason behind this could be that you often get numbers that cannot be represented exactly in binary, so you would get a long mantissa; for instance, 0.01 is represented internally as 0.00999... as you can see by writing:
float f = 0.01f;
Console.WriteLine ("{0:G}", f);
Console.WriteLine ("{0:G}", (double) f);
by rounding at the seventh digit, you will get back "0.01", which is what you would have expected.
For what seen above, numbers with only 7 digits will not show this problem, as you already saw.
Just to be clear: the rounding is taking place only when you convert your number to a string: your calculations, if any, will use all the available bits.
Внешняя точность чисел с плавающей запятой составляет 7 цифр (внутренняя - 9), поэтому, если вы превысите это значение, округление (с потенциальными причудами) будет автоматическим. Если вы уменьшите число с плавающей запятой до 7 цифр (например, 1 слева, 6 справа), то это сработает, как и преобразование строки.
Что касается бонусных баллов:
Почему ты ? Потому что этот код был готов взорвать вас. (Вулкан ... удар ... хорошо. Хромой. Пунт. Всегда)
Вы должны ожидать этого при использовании чисел с плавающей запятой, основная причина в том, что компьютер обрабатывает двоичные данные, и это не отображается в точности в десятичные числа.
Для наглядного примера проблем между разными основаниями рассмотрим дробь 1/3. Он не может быть представлен точно в десятичном формате (это 0,333333 .....), но может быть в троичном формате (как 0,1).
Обычно эти проблемы намного менее очевидны при использовании удвоений за счет затрат на вычисление (удвоение количества битов, которыми нужно манипулировать). Однако, учитывая тот факт, что уровень точности поплавка был достаточным, чтобы доставить человека на Луну, вам действительно не стоит зацикливаться :-)
Эти проблемы являются своего рода 101-й компьютерной теорией (в отличие от 101-го программирования - что вы, очевидно, далеко за его пределами), и если вы направляетесь в сторону кода Direct X, где подобные вещи могут появляться регулярно, я бы предположил, что было бы неплохо возьмите книгу по основам компьютерной теории и быстро прочтите ее.
Посмотрите, как они это реализовали (например, в asm).
Может быть, они хотели быть быстрее и сделали что-то вроде:
l = 1 / v.length();
return new Vector3(v.X * l, v.Y * l, v.Z * l);
обменять 2 деления на 3 умножения (потому что они думали, что множители были быстрее, чем деления (что для современных fpus чаще всего недействительно)). Это вводило на один уровень больше операций, поэтому меньше точность.
Это часто упоминаемая «преждевременная оптимизация».
Я думаю, что это правильный ответ, хотя я не согласен с тем, что это плохая оптимизация. В большинстве нетривиальных 3D-приложений векторы нормализованы как много, и я думаю, что fmul по-прежнему намного быстрее, чем fdiv на большинстве процессоров.
Основная причина того, что это плохая оптимизация, состоит в том, что она менее точна. Но даже если div занимает больше времени, чем mult, я бы предположил, что версия 3 div быстрее, потому что 3 параллельных операции вычисляются параллельно (MMX / SSE). Это означает, что время версии 3 div для 1 div, в то время как другое занимает 1 div + 1 mul.
Я попробовал описанный выше метод, и он по-прежнему дает те же результаты.
Это не должно ничего значить, возможно, ваш компилятор умен и правильно его оптимизирует, а реализация DirectX написана вручную (или, может быть, они используют double внутри). Однозначный ответ возможен только тогда, когда мы взглянем на asm DirectX Normalize, все остальное - предположения.
Если ваш код не работает из-за минутных ошибок округления с плавающей запятой, то я боюсь, что вам нужно исправить это, поскольку они просто реальность.
Легко смеяться, но можно ли создать лучшую версию?