Загрузить архив с примерами ЗДЕСЬ.
Давайте посмотрим на следующий код:
#include <windows.h> #pragma comment (lib,"winmm.lib") #include <iostream> using namespace std; int main() { //время начала цикла DWORD start; //время конца цикла DWORD end; //переменная для сложения целых чисел int iret = 0; //переменная для сложения чисел с плавающей точкой float fret = 0; //время сложения целых чисел float it; //время сложения чисел с плавающей точкой float ft; //работаем с целыми числами start = timeGetTime(); for ( unsigned int i = 0; i < 100000000; i++ ) { iret += 1; } end = timeGetTime(); it= (end - start) / 1000.0f; //закончили работать с целыми числами //работаем с числами с плавающей запятой start = timeGetTime(); for ( unsigned int i = 0; i < 100000000; i++ ) { fret += 1.0f; } end = timeGetTime(); ft= (end - start) / 1000.0f; //закончили работать с числами с плавающей запятой //выодим результат на экран cout << "Adding int time: " << it << endl; cout << "Adding float time: " << ft << endl; return 0; }
Что делает это код? В первом цикле мы складываем 100000000 чисел int, во втором цикле мы складываем 100000000 чисел float и засекаем время начала и конца обеих циклов. В результате работы этой программы мы увидим следующий экран:
Как видим сложение чисел int происходит во времени больше чем в два раза быстрее, чем сложение чисел float.
Давайте посмотрим еще один пример кода:
#include <iostream> using namespace std; int main() { int a1 = 1 << 8; int a2 = 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2; cout << a1 << endl; cout << a2 << endl; return 0; }
Операция левого поразрядного сдвига << или правого поразрядного свдига >> выполняюстя быстрее чем операции умножения или деления. Поэтому расчет a1 и a2 выдадут один тот же результат число 256 но расчет a1 с использованием свига будет выполнятся быстрее. Операция порязрядного свига влево - это умножение на 2, операция поразрядного сдвга вправо- это деление на 2. Поэтому иногда числа сдвигаются влево или вправо на определенное число бит, что бы не делить или не умножать на 2 - свиг работает быстрее.
Пример как работает арифметика с фиксированной точкой можно загрузить /src/02.005-fixed_math/Sample_Fixed_Math. Код примера показан ниже.
#include <windows.h> #include <math.h> #define FIXP26_SHIFT 26 //используется для 1/z буферизации #define FIXP22_SHIFT 22 //используется для u/z, v/z наложения текстуры с учетом перспективы int main(void) { //берем глубину сцены int z = 15; //берем текстурные координаты int tu = 255; //переводим значения для арифметики с фиксированной точкой int iz = ( 1 << FIXP26_SHIFT ) / z; int itu = ( tu << FIXP22_SHIFT ) / z; //проивзодим необходимые расчеты со значением itu и iz //например интерполируем значения //ztu = 255 = tu //получаем обратно текстурные координаты //с учетом глубины (текстурирование //с учетом перспективы) для извлечения //пикселя из массива пикселей (изображение rgb) int ztu = (itu << (FIXP26_SHIFT - FIXP22_SHIFT )) / iz; return 0; }
Код примера - текстурирование куба с применением арифметики с фиксированной точкой - можно загрузить /src/02.005-fixed_math/Perspective_Tex1_Fixed_Math.
В чем заключается смысл математики с фиксированной точкой? Мы float число преобразуем в int. Далее выполняем с этим int необходимые вычисления. Затем обратно преобразуем int в float и используем этот float где нам нужно в программе.
Почему это делается? Процессор в компьютере гораздо быстрее работает с целыми числами чем с числами с плавающей запятой. То есть все операции с числами int выполняются быстрее чем с числами float.
Мы можем float число превратить в int если умножить float на 65536 к примеру. Или можно привести float к int и этот int сдвинуть на 16 разрядов в влево - то есть то же что умножить на 65536, но только операцию свига процессор выполняет быстрее чем умножение. Потом мы выполняем с этим int необходимые вычисления, и когда нужно его использовать преваращем в float сдвигая int на 16 разрадов в право (что аналогично делению на 65536).
В следующем примере показано как делали в старых играх для сокращения расчетов. Пример можно загрузить /src/02.005-fixed_math/3D_Cube_INT. Используются заранее вычисленные таблица синусов, косинусов, и глубины ZTable. За 360 градусов принимали число 65536, один градус в этом случае равен 182, далее выполняли целочисленные расчеты, избегая float значений. В примере когда ассемблерная функция возвращает значение синуса или косинуса, значение имеет диапазон от 0 до 16384 который накладывается на обычный диапазон от 0 до 1 (см.конец таблицы в файле MATH.ASM там максимальное значение равно 0x4000 что в десятичном виде 16834 или в обычном значении 1). В примере используется математика с фиксированной точкой. Здесь в коде есть косинус делить на синус - это будет котангенс, или 1 поделить на тангенс, что соответствует расчету матрицы проекции. ZTable создавалсь в примере для сокращения математических вычислений (и экономии времени) в каждом кадре, так делалось когда то на медленных компьютерах.