Как определить целесообразность использования чисел с фиксированной точкой в ​​заданном (игровом) проекте?

Я рассматривал возможность публикации этого вопроса на GameDev, так как мой случай связан с игровым проектом, но я решил, что это больше подходит для более широкого программирования SE. . Пожалуйста, дайте мне знать, если этот вопрос будет лучше размещен там в конце концов.

Насколько я понимаю — и, пожалуйста, поправьте меня, если я ошибаюсь — разработка игр для мобильных устройств (в моем случае) может значительно выиграть от использования вычислений с фиксированной точкой, поскольку это обеспечит большую согласованность между платформами и повысит производительность. случаях, когда устройство не приспособлено для правильной обработки операций с плавающей запятой.

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

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

CIwVec2 calculateBezier(float t, CIwVec2 p0, CIwVec2 p1, CIwVec2 p2) {
    float u = 1 - t;

    CIwVec2 p = IW_FIXED(u * u) * p0;   // (1 - t)²P0
    p += IW_FIXED(2 * u * t) * p1;      // 2(1 - t)tP1
    p += IW_FIXED(t * t) * p2;          // t²P2

    return p;
}

В этом проекте я использую Marmalade SDK, который использует C++ и поставляется с собственной реализацией чисел с фиксированной точкой (у них есть как 16-, так и 32-разрядные, я использую 32-разрядные на данный момент) и класс Vector (CIwVec2), который использует эту реализацию для позиционирования и вычислений (включая скалярное умножение, которое показано в коде выше). О, и IW_FIXED — это просто макрос для преобразования чисел с плавающей запятой в числа с фиксированной точкой.

Когда я пытаюсь запустить приведенный выше код, я получаю сообщение об ошибке Multiply overflow. Отладьте значения следующим образом:

t = 0
u = 1 (which converts to 4096 in int32 fixed-point with IW_FIXED)
p0.x = 1638400 (400.0f with IW_FIXED_TO_FLOAT)
p0.y = 409600 (100.0f with IW_FIXED_TO_FLOAT)

Честно говоря, у меня нет полного представления о числах с фиксированной точкой. Я понимаю эту идею, но операции с фиксированной точкой мне не совсем понятны (должно быть, я пропустил большинство математических занятий, связанных с основанием 2, позор мне). Но меня совершенно сбивает с толку тот факт, что что-то такое простое, как 1.0f * 400.0f, может вызвать переполнение в фиксированной точке.

Итак, хотя я думал, что у меня не будет проблем с поддержкой фиксированной точки в моем проекте, похоже, что это может быть не так. Игра представляет собой автомобильную игру с видом сверху, в которой не будет огромных трасс или чего-то еще, но они должны быть как минимум такими же большими, как экран устройства (или, что еще лучше, его разрешение), и, поскольку мы стремимся для планшетов также проблемы с чем-то вроде 1.0f * 400.0f означают, что о фиксированной точке не может быть и речи.

Я прав с этим предположением? И для будущих проектов и для других людей с похожими проблемами, как мы можем оценить жизнеспособность чисел с фиксированной точкой в ​​​​проекте? Кроме того, как выбрать между 16-битным и 32-битным было бы отличным бонусом :)

(Извините за длинный пост и спасибо за ваше время!)

Обновление:

Итак, подытожим немного ответов на данный момент. Идеальным сценарием было бы реализовать ваши числа с фиксированной точкой таким образом, чтобы иметь необходимый диапазон для ваших нужд (ответ Mooing Duck). Кроме того, для операций с 32-битными числами безопаснее всего рассчитывать с использованием 64-битных (ответ timday и комментарий Макса). Кстати, в Marmalade есть некоторые функции «безопасного фиксированного умножения», но это не относится к перегрузке оператора скалярного умножения для CIwVec2 (который использует IW_FIXED_MUL внизу, что не обеспечивает безопасного умножения).

И, наконец, что касается моего конкретного сценария, похоже, что начиная с Marmalade 6.1 простое использование поплавков, вероятно, было бы лучшим решением.

Редактировать: хотя ответ Макса действительно решил мою проблему, в основном потому, что это было что-то конкретное для Marmalade. Из-за этого я выбрал ответ Mooing Duck в качестве выбранного ответа, так как я чувствую, что он поможет большему количеству людей в целом.


person Vexille    schedule 02.10.2012    source источник
comment
Ваш код показывает переполнение 100,0 * 400,0, но текст относится к переполнению 1,0 * 400,0.   -  person timday    schedule 03.10.2012
comment
@timday: проверьте еще раз, Вексиль прав. это 1.0*400.0 за которым следует 1.0f * 100.0f.   -  person Mooing Duck    schedule 03.10.2012
comment
Точно, это в базовой реализации скалярного умножения. (u * u) оценивается как 1,0f (затем преобразуется в 4096 в фиксированной точке), а затем умножается на p0.x, затем на p0.y. Однако код никогда не доходит до p0.y :P   -  person Vexille    schedule 03.10.2012
comment
Вы случайно не ориентируетесь на 15-летние мобильные устройства? В противном случае нет разумной причины использовать арифметику с фиксированной точкой. Ни с точки зрения производительности, ни с точки зрения согласованности.   -  person jalf    schedule 04.10.2012


Ответы (3)


Фиксированную точку можно рассматривать как хранящую верхнюю половину дроби, а нижняя половина в вашем случае равна 4096. Таким образом, 1.0f равно 4096/4096 и хранится в памяти как 4096. Я не буду вдаваться в подробности. дробного умножения, потому что я их не помню, но важная часть заключается в том, что при умножении A/D * B/D получается C/D, тогда C равно A*B/D.

Итак: там, где у вас 1.0f * 400.0f, компьютер видит это как 4096 * 1638400 / 4096. Немного алгебры показывает, что это должно привести к 1638400 (400.0f), но если библиотека дробных точек недостаточно умна для этого, она получит временное значение 6710886400 до того, как будет выполнено деление, что слишком велико для int .

Поскольку ваши числа с плавающей запятой имеют знаменатель 4096, ваши числа с плавающей запятой точны до ближайшего 0,00024 и имеют диапазон от -524288,999f до 524287,999f (приблизительно). Есть ли в вашем компиляторе способ уменьшить точность поплавков, чтобы получить больший диапазон? В противном случае вы из шланга.

РЕДАКТИРОВАТЬ

Макс подтверждает, что 4096 является частью Marmalade и не может быть изменен. В этом случае я вижу несколько вариантов:
(1) Не использовать фиксированную точку.
(2) Старайтесь, чтобы все числа с плавающей запятой находились в диапазоне от -128 до 128.
(3) Используйте специальную функцию для умножения скаляра и CIwVec2, которая использует IW_FIXED_MUL внизу. Желательно заключить float в специальный класс и перегрузить operator* для вызова IW_FIXED_MUL. Не выполняйте неявное преобразование в float, иначе вы никогда не найдете всех ошибок.

person Mooing Duck    schedule 02.10.2012
comment
`4096*1638400/4096` недостаточно. Это общая формула, которая не учитывает типы. Вы должны использовать типы int64 для вычислений, если вы используете 32-битные значения. Marmalade предоставляет такую ​​функцию фиксированной точки IW_FIXED_MUL - person Max; 03.10.2012
comment
Ваш способ снижения точности точно такой же, как мой с плавающей запятой! Кто бы это сказал? - person TonyK; 03.10.2012
comment
@TonyK Он имеет в виду использование знаменателя, такого как 512, вместо 4096, что не совпадает с плавающей запятой. - person CrazyCasta; 03.10.2012
comment
@Max IW_FIXED_MUL предназначен для двух чисел с фиксированной точкой. Перегрузка множителя на CIwVec2 должна выполнять IW_FIXED_MUL внутри. - person CrazyCasta; 03.10.2012
comment
@CrazyCasta Вы не можете использовать 512. 4096 жестко закодирован в Marmalade SDK. - person Max; 03.10.2012
comment
Тогда ответ не то же самое, что... ответ в том, что изменить знаменатель невозможно. - person CrazyCasta; 03.10.2012
comment
@Max: я до сих пор не понимаю, что вы сказали о том, что 4096 * 1638400 / 4096 недостаточно. Вот как работает базовая архитектура, которая может вызвать наблюдаемое переполнение. Marmalade, похоже, не использует IW_FIXED_MUL при умножении числа с плавающей запятой и CIwVec2. - person Mooing Duck; 03.10.2012

То, что вы видите, и 32-битные промежуточные продукты, которые он подразумевает (см. ответ Mooing Duck), кажутся мне серьезной слабостью.

Я успешно использовал библиотеки с фиксированной запятой в прошлом (на x86 в эпоху 386/486), и все они использовали 64-битный промежуточный вариант для умножения перед корректирующим сдвигом (на самом деле я, кажется, помню, что Intel предоставила очень хороший 32x32-to-64). инструкция умножения битов, которая идеально подходила для этого). Более высокая промежуточная точность дала вам довольно большую свободу в диапазонах данных; Мне бы очень не хотелось работать с чем-то настолько ограниченным, как эта библиотека Marmalade, но я понимаю, почему им, возможно, пришлось это сделать из соображений переносимости.

person timday    schedule 02.10.2012
comment
Вы правы в том, что 32x32 приводит к 64-битному, а инструкция DIV имеет 64-битный дивиденд. Это даже не особенные, это самые старые формы MUL/DIV x86 (конечно, в 16-битные дни входы были 16-битными, а выходные данные были 32-битными). - person CrazyCasta; 03.10.2012

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

Есть несколько телефонов Android без FPU, но большинство телефонов имеют FPU, GPU с GLES 1.0 и GLES 2.0. Теперь мы ориентируемся на устройства GLES 2.0 с Marmalade.

Причина, по которой у Marmalade есть IW_FIXED, заключается в том, что у них есть собственный графический слой, называемый IwGX.

До сентября 2012 г. (Мармелад 6.1). IwGX не поддерживал плавающую точку. Вот почему разработчики были вынуждены использовать фиксированные номера, даже когда они предназначались для последних устройств GLES 2.0.

Сегодня нет смысла использовать фиксированные номера с мармеладом.

--

В любом случае, если вы все еще хотите умножить с фиксированной точкой, есть IW_FIXED_MUL функция

ДОБАВЛЕНО: код в примере правильный.

person Max    schedule 02.10.2012
comment
Принимая во внимание IW_FIXED_MUL, другим предложением может быть создание класса fixed для автоматического вызова этой функции при умножении фиксированных точек и правильного использования других функций. - person Mooing Duck; 03.10.2012
comment
подождите, обе стороны функции являются частью мармелада, он не может вызвать функцию IW_FIXED_MUL. Эту функцию должен вызывать Marmalade. - person Mooing Duck; 03.10.2012
comment
На самом деле, поскольку скалярное умножение является реализацией Marmalade, он, вероятно, вызывает внутренний вызов IW_FIXED_MUL. Чтобы быть уверенным, я попробовал IW_FIXED_MUL(IW_FIXED(1.0f), IW_FIXED(400.0f)); и все равно получил ошибку переполнения Multiply в этой строке. О том, что вы сказали об использовании поплавков, это очень интересная информация, и я действительно думаю просто использовать поплавки, но не повлияет ли это на производительность на старых телефонах? - person Vexille; 03.10.2012
comment
Существуют серьезные проблемы с фиксированной точкой на Marmalade. madewithmarmalade.com/devnet/forum/float-vs-fixed Несколько разработчики сообщили о пробелах и трещинах в моделях developer.roolez.com/2012 /07/03/мармелад-gx-артефакты - person Max; 03.10.2012
comment
Ого, я этого не знал. Тогда я думаю, что это с плавающей запятой. Спасибо, Макс. - person Vexille; 03.10.2012