Об обработке чисел с плавающей запятой детерминированным способом
Плавающая точка является детерминированной. Ну, так и должно быть. Это сложно.
Существует множество литературы по числам с плавающей запятой:
И как они проблемные:
Для аннотации. По крайней мере, в одном потоке одинаковые операции с одинаковыми данными, происходящие в одном и том же порядке, должны быть детерминированными. Таким образом, мы можем начать с заботы о входах и переупорядочения.
Одним из таких факторов, вызывающих проблемы, является время.
Прежде всего, вы всегда должны вычислять один и тот же временной шаг. Я не говорю, чтобы не измерять время, я говорю, что вы не будете передавать время на симуляцию физики, потому что изменения во времени являются источником шума в симуляции.
Зачем вы измеряете время, если не передаете его в симуляцию физики? Вы хотите измерить прошедшее время, чтобы узнать, когда должен быть вызван шаг симуляции, и - если вы используете сон - сколько времени для сна.
Таким образом:
- Измерьте время: да
- Использовать время в симуляции: нет
Теперь порядок команд.
Компилятор может решить, что f * a + b
это то же самое b + f * a
, что и другой результат. Он также может компилироваться в fmadd , или он может решить взять несколько строк, как это происходит вместе, и написать их с помощью SIMD , или какой-то другой оптимизации, о которой я не могу думать прямо сейчас. И помните, что мы хотим, чтобы одни и те же операции выполнялись в одном и том же порядке, возникает причина, по которой мы хотим контролировать, какие операции происходят.
И нет, использование двойного не спасет вас.
Вам нужно беспокоиться о компиляторе и его конфигурации, в частности, чтобы синхронизировать числа с плавающей запятой по сети. Вы должны получить сборки, чтобы согласиться сделать то же самое.
Возможно, написание сборки было бы идеальным. Таким образом, вы решаете, какую операцию делать. Однако это может быть проблемой для поддержки нескольких платформ.
Таким образом:
Случай для чисел с фиксированной точкой
Из-за того, как плавающие элементы представлены в памяти, большие значения будут терять точность. Дело в том, что сохранение ваших значений маленькими (зажим) смягчает проблему. Таким образом, нет огромных скоростей и нет больших комнат. Это также означает, что вы можете использовать дискретную физику, потому что у вас меньше риск туннелирования.
С другой стороны, небольшие ошибки будут накапливаться. Итак, обрезать. Я имею в виду, масштабировать и приводить к целочисленному типу. Таким образом, вы знаете, что ничего не накапливается. Там будут операции, которые вы можете сделать, оставаясь с целочисленным типом. Когда вам нужно вернуться к плавающей точке, вы применяете и отменяете масштабирование.
Обратите внимание, я говорю масштаб. Идея состоит в том, что 1 единица будет фактически представлена как степень двух (16384, например). Что бы это ни было, сделайте это константой и используйте это. Вы в основном используете его в качестве номера с фиксированной точкой. На самом деле, если вы можете использовать правильные числа с фиксированной точкой из некоторой надежной библиотеки гораздо лучше.
Я говорю обрезать. Что касается проблемы округления, это означает, что вы не можете доверять последнему значению того значения, которое вы получили после приведения. Итак, перед тем как бросить шкалу, нужно получить на один бит больше, чем нужно, а затем обрезать его.
Таким образом:
- Держите ценности маленькими: Да
- Тщательное округление: да
- Фиксированные номера точек, когда это возможно: Да
Подожди, зачем тебе с плавающей точкой? Не могли бы вы работать не только с целочисленным типом? О верно. Тригонометрия и радиация. Вы можете вычислить таблицы для тригонометрии и радиации и запекать их в своем источнике. Или вы можете реализовать алгоритмы, используемые для их вычисления с использованием числа с плавающей запятой, за исключением использования чисел с фиксированной запятой. Да, вам необходимо сбалансировать память, производительность и точность. Тем не менее, вы можете остаться вне чисел с плавающей запятой и оставаться детерминированным.
Знаете ли вы, что они сделали такие вещи для оригинальной PlayStation? Пожалуйста, встречайте мою собаку, патчи .
Кстати, я не говорю, чтобы не использовать с плавающей запятой для графики. Просто для физики. Я имею в виду, конечно, позиции будут зависеть от физики. Однако, как вы знаете, коллайдер не обязательно должен соответствовать модели. Мы не хотим видеть результаты усечения моделей.
Таким образом: используйте фиксированные номера точек.
Для ясности, если вы можете использовать компилятор, который позволяет вам указать, как работает число с плавающей запятой, и этого вам достаточно, то вы можете это сделать. Это не всегда вариант. Кроме того, мы делаем это для детерминизма. Числа с фиксированной точкой не означают, что ошибок нет, ведь они имеют ограниченную точность.
Я не думаю, что «число с фиксированной точкой трудно» является хорошей причиной, чтобы не использовать их. И если вам нужна веская причина для их использования, это детерминизм, в частности детерминизм на разных платформах.
Смотрите также:
Приложение : Я предлагаю сохранить размер мира маленьким. При этом оба OP и Jibb Smart поднимают вопрос о том, что удаление от исходных поплавков имеет меньшую точность. Это повлияет на физику, которая будет видна гораздо раньше, чем край света. Числа с фиксированной точкой, ну, имеют фиксированную точность, они будут одинаково хорошими (или плохими, если хотите) везде. Что хорошо, если мы хотим детерминизма. Я также хочу отметить, что то, как мы обычно занимаемся физикой, обладает свойством усиливать небольшие вариации. Посмотрите Эффект Бабочки - Детерминированная Физика в Невероятной Машине и Создателе Контрацепции .
Еще один способ заниматься физикой
Я думал, что причина, по которой усиливается небольшая ошибка в точности чисел с плавающей запятой, заключается в том, что мы выполняем итерации для этих чисел. На каждом шаге симуляции мы берем результаты последнего шага симуляции и делаем что-то на них. Накопление ошибок на вершине ошибок. Это ваш эффект бабочки.
Я не думаю, что мы увидим, что одна сборка, использующая один поток на одной и той же машине, даст разные результаты при одном и том же вводе. Тем не менее, на другой машине это может, или другая сборка может.
Там есть аргумент для тестирования там. Если мы решим, как именно все должно работать, и сможем протестировать на целевом оборудовании, мы не должны выпускать сборки с другим поведением.
Тем не менее, есть также аргумент для того, чтобы не работать на выезде, который накапливает так много ошибок. Возможно, это возможность заниматься физикой по-другому.
Как вы, возможно, знаете, существует непрерывная и дискретная физика, обе из которых работают над тем, насколько каждый объект продвинется на временном шаге. Тем не менее, непрерывная физика имеет возможность выяснить момент столкновения вместо того, чтобы исследовать различные возможные моменты, чтобы увидеть, произошло ли столкновение.
Таким образом, я предлагаю следующее: используйте методы непрерывной физики, чтобы выяснить, когда произойдет следующее столкновение каждого объекта, с большим временным шагом, намного большим, чем один из одного шага моделирования. Затем вы берете момент ближайшего столкновения и выясняете, где все будет в этот момент.
Да, это много работы одного шага моделирования. Это означает, что симуляция не начнется мгновенно ...
... Однако вы можете смоделировать следующие несколько шагов моделирования без проверки столкновения каждый раз, потому что вы уже знаете, когда произойдет следующее столкновение (или что никакого столкновения не произойдет на большом временном шаге). Кроме того, ошибки, накопленные в этом моделировании, не имеют значения, потому что, как только моделирование достигает большого временного шага, мы просто размещаем позиции, которые мы рассчитали заранее.
Теперь мы можем использовать бюджет времени, который мы использовали бы для проверки столкновений на каждом этапе моделирования, чтобы вычислить следующее столкновение после того, которое мы нашли. То есть мы можем моделировать заранее, используя большой временной шаг. Предполагая, что мир ограничен по объему (это не будет работать для огромных игр), должна быть очередь будущих состояний для симуляции, а затем каждый кадр, который вы просто интерполируете из последнего состояния в следующее.
Я бы поспорил за интерполяцию. Однако, учитывая, что есть ускорения, мы не можем просто интерполировать все одинаково. Вместо этого нам нужно интерполировать с учетом ускорения каждого объекта. В этом отношении мы могли бы просто обновить позицию таким же образом, как мы делаем для большого временного шага (что также означает, что он менее подвержен ошибкам, потому что мы не будем использовать две разные реализации для одного и того же движения).
Примечание : если мы делаем это числа с плавающей запятой, этот подход не решает проблему объектов, ведущих себя по-другому, чем дальше от источника, где они находятся. Однако, хотя верно то, что точность теряется по мере удаления от источника, это все еще детерминировано. Фактически, именно поэтому даже не поднял это первоначально.
добавление
От ОП в комментарии :
Идея состоит в том, что игроки смогут сохранять свои машины в каком-либо формате (например, xml или json), чтобы записывать положение и вращение каждой фигуры. Этот файл xml или json будет затем использован для воспроизведения машины на компьютере другого игрока.
Итак, нет двоичного формата, верно? Это означает, что нам также нужно беспокоиться о том, совпадают ли найденные числа с плавающей запятой или нет с оригиналом. См. Пересмотр точности поплавка: Переносимость поплавка из девяти цифр