Постановка задачи: библиотека для вычислений с фиксированной точкой, и соответствующий тип данных. Подразумеваемое использование: drop-in replacement для стандартного float. На C++. Хочется стандартных для fixed-point вещей: железобетонной повторяемости, фиксированной абсолютной точности, сносной скорости.
Собственно реализация выглядит достаточно тривиальной. Что и приводит к вопросу о архитектуре и дизайне.
Предполагается, что в этой теме лучшие умы gamedev.ru совместными силами изобретут этот дизайн.
Кто тролль? Я тролль?!
При желании тему можно воспринимать, как контрольный в головукирпичик в фундамент GameBoost. Или как разминку для мозгов. Или как повод для болтовни.
Соответственно, в целях плодотворной дискуссии, можно озвучить текущий список Design DecisionsTM (возможно не полный; дополнения допускаются).
1. Целевой стандарт языка (С89/С99/С++03/С++11/С++14/С++17)?
2. Параметризовать ли внутренним типом?
3. Параметризовать ли количеством дробных бит?
4. Или вообще количество дробных бит сделать runtime параметром?
5. Название типа.
6. Поведение при переполнении (UB, как в С++ для встроенных знаковых целых / 2's complement wrap-around / насыщение / runtime ошибка / дать пользователю рубль, пусть сам в столовую ходитпараметр шаблона, задающий поведение / задавать поведение в runtime).
7. Поведение при делении на 0.
8. Округление. IEEE-754 довольно аккуратно относится к округлению, здесь стремиться ли делать то же?
- Round-To-Zero, как в C++ для целых?
- Round-To-Nearest-Ties-To-Even, как по умолчанию в IEEE 754?
- Параметр шаблона?
- Runtime состояние?
9. Infinity/NaN?
10. Доступ к битам?
- Давать напрямую?
- Getter/Setter?
- Не давать вообще?
11. Конструктор по умолчанию?
12. Преобразование из встроенных вещественных?
13. Преобразование к встроенным вещественным?
14. Преобразование к встроенным целым?
15. Математическая библиотека?
16. Лицензия?
17. SIMD?
P. S. изначальная задумка темы Design by Committee была о One True n-Dimensional Vector. Но я подозреваю, что на это я получу ответ "взять glm и не плодить реализации". Стандарта de facto библиотеки чисел с фиксированной точкой как-то особо не заметно. Если C и 16:16, то есть libfixmath, впрочем.
У меня fixed имеет два параметра - внутренний тип и делитель. Делитель - необязательно степень двойки. Например, если я хочу однобайтовое число в интервале [0;1], придётся сделать делитель 255, а не 256. Будет медленнее, но наверное тоже есть более простая формула. А степени двойки компилер должен сам оптимизировать и превратить в сдвиги и маски.
Я не делал никаких встроенных функций, потому что мне fixed нужен только для компактного хранения. Например, у меня в синтезаторе было много захардкоженных массивов во float, и я их перевёл на 1-байтовый нормализованный fixed<byte, 256>. Сэкономил несколько килобайт такой заменой.
Или ещё использовал fixed для более компактных форматов вершин в движке. Например всю позицию вершины я ужал в 4 байта, а было 3 float'а.
В обоих случаях операции над fixed'ами были не нужны, нужно было только редкое преобразование туда-сюда. float вроде всё равно быстрее в наше время, так что вычисления над fixed уже не актуальны.
ИМХО, не нужен.
Полноценный продуманный fixed-point предполагает тщательную слежку за диапазонами (где-то 16:16, где-то 1:31 и т. п.).
Для тяжелой математики часто нужно приводить к правильному диапазону (по типу 0x40000000–0xFFFFFFFF для квадратного корня), т. е. явно выделять мантиссу и порядок.
Кое-где надо сохранять wraparound и работать с нестандартными масштабами (например, полный оборот для углов: 0–232 вместо 0–2π).
В общем, как и любая оптимизация, fixed-point должен затачиваться под конкретную задачу, иначе смысла в оптимизации нет.
gammaker
> В обоих случаях операции над fixed'ами были не нужны, нужно было только редкое преобразование туда-сюда. float вроде всё равно быстрее в наше время, так что вычисления над fixed уже не актуальны.
Вообще-то, даже безотносительно к скорости, есть область, где фиксированная точка предпочтительнее плавающей.
Это хранение и работа с величинами, требующими равномерной точности, не зависящей от расстояния до 0 (трансляционная инвариантность).
В первую очередь, времена и координаты в больших мирах.
}:+()___ [Smile]
> Полноценный продуманный fixed-point предполагает тщательную слежку за
> диапазонами (где-то 16:16, где-то 1:31 и т. п.).
> Для тяжелой математики часто нужно приводить к правильному диапазону (по типу
> 0x40000000–0xFFFFFFFF для квадратного корня), т. е. явно выделять мантиссу и
> порядок.
> Кое-где надо сохранять wraparound и работать с нестандартными масштабами
> (например, полный оборот для углов: 0–232 вместо 0–2π).
> В общем, как и любая оптимизация, fixed-point должен затачиваться под
> конкретную задачу, иначе смысла в оптимизации нет.
балин... а я думал что я что то гениальное придумал :) когда хотел все это запилить
gammaker
> придётся сделать делитель 255
В топку сразу такое. Пока процессор деление произведёт - состаришься.
FordPerfect
> Полноценная математическая библиотека, с std::exp, std::sin
Под вопросом. Где-то для синуса хватит таблица на 32 значения на полный круг, а где-то точность нужна до усрачки.
> Преобразование к встроенным целым?
Через всякие int to_int() не нужно. Надо что-то вроде ceil_to_int/floor_to_int/round_to_int
Не помешали бы беззнаковые fixed величины, но только без явного каста между fixed/ufixed.
Я когда-то написал простой shmup полностью на фикседе: http://www.gamedev.ru/flame/forum/?id=186780
И это было довольно геморно, лишний раз не перемножишь, не напоровшись на переполнение.
FordPerfect
// я мало понимаю в серьёзных кодах, поэтому ткну пальцем в небо
1. старый С++03
2. -
3. биты: я за тупую пару аля UINT + INT (4 милиарда и 9 нолей дробной части)
4. -
5. название: нечто mix32, mix64
6. не-определёность: _забито все биты, кроме бита минуса.
7. деление на ноль: перехватывать и ничего не делать.
8. округление: скользкая тема - глюков от неё очень много.
9. фаза бесконечности: ? только бит минуса ?
зачем это мне ? где буду применять ?
Не знаю, наверно, можно обойтись без этого.
Как конструировать?
Из собственного литерала? ++03 не может.
Из вещественного? Теряешь железность.
Из дроби? Извратненько, но за неимением альтернатив...
gammaker
Ок, если они только формат хранения - то это, вроде, другая задача, с другим дизайном.
У неё своя ценность.
}:+()___ [Smile]
Возможно.
Поэтому я и говорил о чуть более конкретном использовании: drop-in replacement для float (строго одинаковые вычисления +равномерная точность). Думаешь всё равно не заживёт?
Panzerschrek[CN]
>В топку сразу такое. Пока процессор деление произведёт - состаришься.
Целочисленное деление на константу вроде все популярные компиляторы радостно преобразовывают в умножение и сдвиги.
Плюс если там только формат хранения, то целочисленного деления может быть и не возникает.
А вещественное. Можно предрассчитать табличку (конкретно для byte). Ну или на худой конец множить на предрассчитаный 1.0/N и что-то думать про точную представимость 1.0=N/N.
>Через всякие int to_int() не нужно. Надо что-то вроде ceil_to_int/floor_to_int/round_to_int
Выглядит здраво.
>Не помешали бы беззнаковые fixed величины, но только без явного каста между fixed/ufixed.
Вызывает смутные опасения.
Но может и можно сделать так, чтобы (пользователю) не нарваться.
Hardcode
Ок.
Data point принят к сведению.
TarasB
Ну походу, да.
Т. е. либо что-то вроде
fixed(long long numerator,long long denominator);
либо что-то вроде
fixed(int32_t integer_part,uint32_t fractional_part);
и обе одновременно предоставлять мягко говоря решение сомнительное.
И конструктор от числа с плавающей точкой, походу, выкинуть нафиг, а сделать явные from_double_floor(), from_double_to_nearest_ties_to_even() и т. д.
FordPerfect
> Преобразование к встроенным целым?
Чревато багами, проверял на собственном горьком опыте (в коде был operator int). Еще на практике оказалось, что функция from_int не нужна, достаточно явного конструктора. А конструктор типа fixed(int i, int frac) используется крайне редко, но может это специфика.
FordPerfect
> Ну походу, да.
У меня есть такие извраты:
class Fixed { ... Fixed(int a, int b) : g( ( int)( ( ( ( long long)a << BP) + ( b/2))/b) ) {} ... }; inline Fixed DoubleToFixed ( double a) { return Fixed( Fixed::RAW, int( a*double( 1<<Fixed::BP))); } inline Fixed fxd( int a) { return Fixed( a,10); } inline Fixed fxh( int a) { return Fixed( a,100); } inline Fixed fxt( int a) { return Fixed( a,1000); } const Fixed fx0 = 0, fx1 = 1, fx2 = 2, fx3 = 3, fx4 = 4, fx5 = 5, fx6 = 6, fx7 = 7, fx8 = 8, fx9 = 9, fx10 = 10, fxEps( Fixed::RAW,1), fxHalf( 1,2);
Короче, нафиг это говно, пили под версию с пользовательскими литералами. Кстати, с какой студии они есть?
TarasB
User-defined floating-point literal с compile-time парсингом строчки символов и внятным округлением?
Нескучно.
Или ты чисто про целочисленные?
TarasB
Ну хотя
template<char ... Args> int operator ""_fix() { char s[sizeof...( Args)+1]={Args...,0}; return printf( "%s",s); }
выглядит нестрашно.
И сам парсинг... тоже делается.
TarasB
>Кстати, с какой студии они есть?
https://msdn.microsoft.com/en-us/library/hh567368.aspx
Visual Studio 2013 No Visual Studio 2015 Yes
FordPerfect
> User-defined floating-point literal с compile-time парсингом строчки символов и
> внятным округлением?
Да.
FordPerfect
> Нескучно.
Да ничё сложного, мне кажется. Или там ограничения на императивность?
Тема в архиве.