Почему разрешение чисел с плавающей запятой уменьшается дальше от начала координат?

19

Моя сцена OpenGL содержит объекты, которые расположены на смехотворно большом расстоянии от источника. Когда я рассматриваю эти объекты и поворачиваю / поворачиваю / масштабирую камеру вокруг них, они «дрожат». То есть вершины, составляющие объекты, кажутся привязанными к воображаемой трехмерной сетке точек. Я читал, что это распространенная проблема из-за большого количества информации, которая может быть сохранена с использованием точности с плавающей запятой (которую использует OpenGL и почти все остальное). Я не понимаю, почему это происходит, хотя.

При поиске решения я наткнулся на очень простое исправление «плавающее начало», и оно, похоже, работает. Я просто трансформирую все, чтобы мои объекты находились в одинаковых относительных положениях, но то, на что смотрит моя камера, близко к началу координат. Я нашел объяснение здесь: http://floatingorigin.com/ , но я не мог следовать ему.

Итак ... Может ли кто-нибудь объяснить, почему расположение моей сцены очень далеко (скажем, 10 миллионов единиц) от источника приводит к неустойчивому поведению, которое я наблюдал? А также почему перемещение его ближе к источнику решает проблему?

Прис
источник
4
Потому что если бы они этого не сделали, они были бы номерами с фиксированной точкой. Тавтологический вопрос, это.
MSalters
1
Правда, но только тогда, когда вы понимаете, что на самом деле означает «с плавающей точкой».
Kylotan

Ответы:

26

Это ВСЕ из-за того, как плавающие точки представлены в компьютерах.

Целые числа хранятся довольно просто; каждая единица в точности равна «единице» от «предыдущей», как и следовало ожидать с счетными числами.

С числами с плавающей запятой это не совсем так. Вместо этого, несколько битов указывают на экспоненту, а остальные указывают на то, что известно как мантисса , или дробная часть, которая затем умножается на экспонентную часть (неявно 2 ^ exp) для получения окончательного результата.

Посмотрите здесь для визуального объяснения битов.

Именно из-за того, что этот показатель является действительной частью битов, точность начинает уменьшаться, когда числа увеличиваются.

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

2 * 2 ^ 2 = 8

3 * 2 ^ 2 = 12

4 * 2 ^ 2 = 16

...и т.д.

Эти числа не очень сильно расходятся только на показателе 2. Но теперь давайте попробуем показатель 38:

2 * 2 ^ 38 = 549755813888

3 * 2 ^ 38 = 824633720832

4 * 2 ^ 38 = 1099511627776

Вау, огромная разница сейчас!

Этот пример, хотя он и не предназначен специально для ОЧЕНЬ СЛЕДУЮЩЕЙ СЧЕТЧИКИ (это будет следующая дробная часть в зависимости от того, сколько битов), демонстрирует потерю точности при увеличении чисел. «Следующая счетная» единица в числах с плавающей точкой очень мала с маленькими показателями и ОЧЕНЬ велика с большими показателями, тогда как в целых числах это ВСЕГДА 1.

Причина того, что метод источника с плавающей запятой работает, заключается в том, что он масштабирует все эти числа с плавающей запятой с потенциально большим показателем ВНИЗ К маленькому показателю, чтобы «следующие счетные числа» (точность) могли быть очень маленькими и счастливыми.


источник
Примеры, которые вы привели, были действительно иллюстративными, спасибо :)
Pris
3
На правильном пути, но я хотел бы, чтобы вы использовали примеры, которые ближе к тому, как с плавающей точкой действительно работает. Это не возносит мантиссу к показателю степени; это мантисса * 2 ^ показатель.
Натан Рид
3
Вы правы, я знал это; Я не знаю, о чем я думал. Отредактировал мой ответ.
1
@ ScottW Хорошее редактирование! +1
Натан Рид
17

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

http://en.wikipedia.org/wiki/Single_precision

Когда вы получаете все большие и большие числа, у вас просто не хватает битов для представления меньших частей.

тетрада
источник
8

Классика в этой области должна быть воспитана: что должен знать каждый компьютерщик о числах с плавающей запятой .

Но суть этого в том, что числа с плавающей запятой одинарной (двойной) точности представляют собой просто 32-битное (64-битное) двоичное число с 1-битным знаком, представляющим знак, 8-битный (11-битный) показатель степени 2 и 23-битное (52-битное) значениеand (в скобках указаны значения для двойных чисел).

Это означает, что наименьшее положительное число, которое вы можете представить с одинарной точностью, составляет 0,0000000000000000000001 x 2 -127 = 2 -22 x 2 -127 = 2 -149 ~ 1,40 x 10 -45 .

Следующее положительное число в два раза больше: 0,0000000000000000000010 x 2 -127 = 2 -148 ~ 2,80 x 10 -45 , а затем следующее число является суммой двух предыдущих 0,0000000000000000000011 x 2 -127 = 3 x 2 -149 ~ 4.2 - 45 .

Это продолжает увеличиваться на ту же постоянную разницу до тех пор, пока: 0,1111111111111111111111 x 2 -127 = 2 -126 - 2 149 ~ 1,17549435 x 10 -38 - 0,00000014 x 10 -38 = 1,17549421 x 10 -38

Теперь вы достигли нормальных чисел (где первая цифра мантиссы 1) а именно: +1,0000000000000000000000 х 2 -126 = 2 -126 = 1,17549435 х 10 -38 и следующее число , то +1,0000000000000000000001 х 2 -126 = 2 -126 (1 + 2 -22 ) = 1,17549435 х 1,00000023.

доктор джимбоб
источник
2

Причина, по которой числа с плавающей запятой становятся менее точными вдали от источника, заключается в том, что число с плавающей запятой должно представлять большие числа. То, как это делается, дает ему термин «с плавающей точкой». Он разделяет возможные значения, которые он может принимать (который определяется его длиной в битах), так что для каждого показателя степени примерно одинаковое число: для 32-разрядного числа с плавающей запятой 23 из битов определяют мантиссу или значение. Таким образом, он сможет принимать значение 2 ^ 23 различных значений в каждом диапазоне показателей. Один из этих диапазонов экспонент равен 1-2 [2 ^ 0 до 2 ^ 1], поэтому разбиение диапазона от 1 до 2 на 2 ^ 23 различных значений обеспечивает большую точность.

Но разделение диапазона [2 ^ 10 до 2 ^ 11] на 2 ^ 23 различных значений означает, что расстояние между каждым значением намного больше. Если бы этого не было, то 23 бита было бы недостаточно. Все дело в компромиссе: вам нужно бесконечное количество бит для представления любого действительного числа. Если ваше приложение работает таким образом, что позволяет вам обходиться с более низкой точностью при больших значениях, и вы извлекаете выгоду из возможности фактически представлять большие значения , вы используете представление с плавающей запятой.

Стивен Лу
источник
просто сделаю заметку здесь при дальнейшем рассмотрении 7 лет спустя ... мои цифры в моих примерах не особенно удачно выбраны. Но общие баллы действительны.
Стивен Лу
1

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

мантисса × 10 экспонента

Когда показатель степени равен 0, каждое целое число в диапазоне 0–999 может быть представлено точно. Когда это 1, вы, по сути, умножаете каждый элемент этого диапазона на 10, так что вы получаете диапазон 0–9990; но теперь, только кратные 10 могут быть представлены точно, потому что у вас все еще есть только три цифры точности. Когда показатель степени равен максимуму 9, разница между каждой парой представимых целых чисел составляет один миллиард . Вы буквально торгуете точностью ради дальности.

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

Джон Перди
источник
0

В общем, разрешение ухудшается, потому что разрешение умножается на значение показателя степени (2 ** часть показателя).

в подтверждение комментария Джоша: вышеупомянутое было просто, чтобы поместить ответ в краткое заявление. Конечно, как я пытался указать на http://floatingorigin.com/ , это только начало общего решения, и ваша программа может иметь дрожание из ряда мест: в конвейере точности или других частях кода. ,

Крис Торн
источник
Это не добавляет ничего, чего еще нет в других ответах.
Верно: я понял, что могу описать ответ одной строкой, и подумал, что кто-то может найти краткий ответ полезным.
Крис Торн
-1

Буфер глубины OpenGL не является линейным . Чем дальше, тем хуже разрешение. Я рекомендую прочитать это . Что-то взято оттуда (12.070):

Таким образом, перспективное деление по своей природе вызывает большую точность Z близко к передней части объема обзора, чем к задней части.

И еще один (12.040):

Возможно, вы настроили плоскости отсечения zNear и zFar таким образом, что это сильно ограничивает точность буфера глубины. Обычно это вызвано тем, что значение плоскости отсечения zNear слишком близко к 0.0. Поскольку плоскость отсечения zNear устанавливается все ближе к 0.0, эффективная точность буфера глубины резко уменьшается. Перемещение плоскости отсечения zFar дальше от глаза всегда отрицательно влияет на точность буфера глубины, но это не так драматично, как перемещение плоскости отсечения zNear.

Таким образом, вы должны переместить свой ближний отсеченный самолет как можно дальше, а дальний - как можно ближе.

zacharmarz
источник
-1: вопрос касается точности с плавающей точкой, а не проблем точности с нелинейным представлением буфера глубины.
Натан Рид
Возможно, что я вижу из-за проблем с буферизацией глубины. Я использую библиотеку поверх OpenGL для просмотра своей сцены, и я предполагаю, что она устанавливает камеру, вид, а также ближнюю и дальнюю плоскости отсечения, чтобы учитывать размер и положение геометрии (так как зритель инструмент, кажется, автоматически устанавливает оптимальный вид для содержимого сцены). Но я думаю, что это может быть не так - я попробую поиграться с плоскостями отсечения, оставив исходную позицию без изменений, и посмотрю, что произойдет.
Pris
2Nathan Reed: Автор написал, что у него сцена OpenGL, поэтому я подумал, что это тоже может быть проблемой.
zacharmarz
Эта проблема может показаться похожей или связанной, но значения буфера глубины определенно НЕ хранятся способом, совместимым с числами с плавающей запятой. Это формат с фиксированной запятой. Именно поэтому ответ может вводить в заблуждение.
Стивен Лу