Несогласованность Microsoft.DirectX.Vector3.Normalize ()

Два способа нормализовать объект 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

Почему?

(Бонусные баллы: Почему я?)

Легко смеяться, но можно ли создать лучшую версию?

shoosh 27.11.2008 11:45

Я уже предоставил лучшую версию. Прочитай заново.

Agnel Kurian 27.11.2008 11:53

Один из моих коллег указал, что флаг D3DCREATE_FPU_PRESERVE может исправить это поведение. Подробнее: goo.gl/YTiEB

Agnel Kurian 27.05.2011 10:17
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
1 422
5

Ответы 5

Плевать на это. При использовании поплавков всегда есть какая-то ошибка. Если вам интересно, попробуйте переключиться на удвоение и посмотрите, происходит ли это по-прежнему.

У вас есть интересное обсуждение о строковом форматировании чисел с плавающей запятой.

Просто для справки:

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 на большинстве процессоров.

DarthPingu 27.11.2008 12:17

Основная причина того, что это плохая оптимизация, состоит в том, что она менее точна. Но даже если div занимает больше времени, чем mult, я бы предположил, что версия 3 div быстрее, потому что 3 параллельных операции вычисляются параллельно (MMX / SSE). Это означает, что время версии 3 div для 1 div, в то время как другое занимает 1 div + 1 mul.

flolo 27.11.2008 12:37

Я попробовал описанный выше метод, и он по-прежнему дает те же результаты.

Agnel Kurian 27.11.2008 12:47

Это не должно ничего значить, возможно, ваш компилятор умен и правильно его оптимизирует, а реализация DirectX написана вручную (или, может быть, они используют double внутри). Однозначный ответ возможен только тогда, когда мы взглянем на asm DirectX Normalize, все остальное - предположения.

flolo 27.11.2008 12:53

Если ваш код не работает из-за минутных ошибок округления с плавающей запятой, то я боюсь, что вам нужно исправить это, поскольку они просто реальность.

Другие вопросы по теме