Microsoft.DirectX.Vector3.Normalize () несоответствие - PullRequest
0 голосов
/ 27 ноября 2008

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

Почему?

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

Ответы [ 5 ]

2 голосов
/ 27 ноября 2008

Посмотрите, как они это реализовали (например, в asm).

Может быть, они хотели быть быстрее и создали что-то вроде:

 l = 1 / v.length();
 return new Vector3(v.X * l, v.Y * l, v.Z * l);

для торговли 2 делениями против 3 умножений (потому что они думали, что mults были быстрее, чем div (что для современного fpus чаще всего недопустимо)). Это вводило на один уровень больше операций, поэтому меньше точности.

Это часто упоминаемая «преждевременная оптимизация».

0 голосов
/ 27 ноября 2008

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

0 голосов
/ 27 ноября 2008

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

Только для справки:

Для представления вашего номера требуется 24 бита, что означает, что вы израсходовали всю мантиссу с плавающей точкой (23 бита + 1 подразумеваемый бит).
Функция Single.ToString () в конечном итоге реализована встроенной функцией, поэтому я не могу точно сказать, что происходит, но я предполагаю, что она использует последнюю цифру для округления всей мантиссы.
Причиной этого может быть то, что вы часто получаете числа, которые не могут быть представлены точно в двоичном виде, поэтому вы получите длинную мантиссу; например, 0,01 представляется внутренне как 0,00999 ... как вы можете видеть написав:

float f = 0.01f;
Console.WriteLine ("{0:G}", f);
Console.WriteLine ("{0:G}", (double) f);

округляя седьмую цифру, вы получите обратно "0,01", чего вы и ожидали.

Для того, что вы видели выше, цифры, состоящие только из 7 цифр, не будут показывать эту проблему, как вы уже видели.

Просто чтобы прояснить: округление происходит только тогда, когда вы преобразовываете свое число в строку: ваши вычисления, если таковые имеются, будут использовать все доступные биты.

Плавающие имеют точность 7 цифр внешне (9 внутри), поэтому, если вы превысите это значение, то округление (с потенциальными причудами) будет автоматическим.
Если вы опустите число до 7 цифр (например, 1 слева, 6 справа), то это сработает, и преобразование строк также будет выполнено.

Что касается бонусных баллов:

Почему ты? Потому что этот код был «готов напасть на тебя».
(Вулкан ... удар ... хорошо. Lamest. Punt. Когда-нибудь)

0 голосов
/ 27 ноября 2008

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

Для наглядного примера проблем между различными базами рассмотрим дробь 1/3. Он не может быть представлен точно в десятичном виде (это 0,333333 .....), но может быть в троичной (как 0,1).

Как правило, эти проблемы намного менее очевидны с двойными числами за счет вычислительных затрат (удвоение количества бит для манипуляции). Однако, учитывая тот факт, что уровень точности плавания был достаточен для того, чтобы доставить человека на Луну, вам действительно не следует зацикливаться: -)

Эти проблемы являются своего рода компьютерной теорией 101 (в отличие от программирования 101 - которое, очевидно, далеко выходит за рамки), и если вы движетесь к коду Direct X, где подобные вещи могут появляться регулярно, я бы предположил, что хорошая идея - взять основную книгу по компьютерной теории и быстро ее прочитать.

0 голосов
/ 27 ноября 2008

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

...