Как я могу получить согласованное поведение программы при использовании float? - PullRequest
11 голосов
/ 08 марта 2012

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

В случае, когда результат равен нулю или меньше, что-то происходит.Пока это кажется простым - я могу просто сделать что-то подобное для каждого узла:

if (value <= 0.0f) something_happens();

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

Вот простой пример программы, которая демонстрирует явления, которые я описываю:

float f1 = 0.000001f, f2 = 0.000002f;
f1 += 0.000004f; // This part happens first here
f1 += (f2 * 0.000003f);
printf("%.16f\n", f1);

f1 = 0.000001f, f2 = 0.000002f;
f1 += (f2 * 0.000003f);
f1 += 0.000004f; // This time this happens second
printf("%.16f\n", f1);

Вывод этой программы

0.0000050000057854
0.0000050000062402

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

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

Единственное решение, о котором я до сих пор мог придумать, - это использовать какое-то значение epsilon, например:

if (value < epsilon) something_happens();

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

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

Примечание: использование целочисленных значений вместо этого не вариант.


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

Ответы [ 5 ]

4 голосов
/ 08 марта 2012

Я работал с имитационными моделями в течение 2 лет, и эпсилон-подход - самый разумный способ сравнить ваши поплавки.

3 голосов
/ 08 марта 2012

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

  • Если ваши значения находятся в известном диапазоне, и вам не нужны деления, вы можете масштабировать проблему и использовать точные операции над целыми числами. Как правило, условия не применяются.
  • Разновидностью является использование рациональных чисел для точных вычислений. Это по-прежнему имеет ограничения на доступные операции и, как правило, имеет серьезные последствия для производительности: вы обмениваете производительность на точность.
  • Режим округления можно изменить. Это может быть использовано для вычисления интервала, а не отдельного значения (возможно, с 3 значениями, полученными в результате округления вверх, округления вниз и округления ближе всего). Опять же, это не будет работать для всех, но из этого вы можете получить оценку ошибки.
  • Отслеживание значения и количества операций (возможно, нескольких счетчиков) также может использоваться для оценки текущего размера ошибки.
  • Чтобы поэкспериментировать с различными числовыми представлениями (float, double, интервалом и т. Д.), Вы можете реализовать моделирование в виде шаблонов, параметризованных для числового типа.
  • Существует много книг об оценке и минимизации ошибок при использовании арифметики с плавающей запятой. Это тема численной математики.

В большинстве случаев мне известно, что я вкратце поэкспериментировал с некоторыми из упомянутых выше методов и пришел к выводу, что модель в любом случае неточна и не беспокоится об усилиях. Кроме того, выполнение чего-то еще, кроме использования float, может дать лучший результат, но слишком медленное, даже использование double из-за удвоенного объема памяти и меньшей возможности использования операций SIMD.

0 голосов
/ 14 марта 2012

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

Как правило, использование порога эпсилон полезно только при сравнении двух чисел с плавающей точкой на равенство, а не при сравнении их, чтобы увидеть, что больше Так что (по крайней мере, для большинства моделей) использование epsilon ничего не даст вам - оно просто изменит набор перевернутых узлов, но не уменьшит его. Если ваша модель хаотична, то она хаотична.

0 голосов
/ 14 марта 2012

Я рекомендую вам один шаг - предпочтительно в режиме сборки - выполнить вычисления, выполняя ту же арифметику на калькуляторе.Вы должны быть в состоянии определить, какие порядки вычислений дают результаты более низкого качества, чем вы ожидаете, и какие это работает.Вы узнаете из этого и, возможно, напишете более упорядоченные вычисления в будущем.

В конце - учитывая примеры чисел, которые вы используете - вам, вероятно, придется принять тот факт, что вы не сможетепроведите сравнения на равенство.

Что касается эпсилон-подхода, вам обычно требуется один эпсилон на каждый возможный показатель степени.Для формата с плавающей запятой одинарной точности вам потребуется 256 значений с плавающей запятой одинарной точности, поскольку показатель степени составляет 8 бит.Некоторые экспоненты будут результатом исключений, но для простоты лучше иметь вектор-член 256, чем проводить большое количество тестов.

Один из способов сделать это - определить базовый эпсилон вслучай, когда показатель степени равен 0, т. е. сравниваемое значение находится в диапазоне 1,0 <= x <2,0.Предпочтительно, чтобы эпсилон был выбран так, чтобы он был адаптирован к основанию 2, т.е. значение, которое может быть точно представлено в формате с плавающей запятой одинарной точности - таким образом, вы точно знаете, с чем вы тестируете, и вам не придется думать о проблемах округления в эпсилоне какЧто ж.Для показателя -1 вы должны использовать базовый эпсилон, деленный на два, для -2, деленный на 4 и так далее.По мере приближения к наименьшей и наивысшей частям диапазона экспоненты вы постепенно исчерпываете точность - по крупицам - поэтому вы должны знать, что экстремальные значения могут вызвать сбой метода epsilon. </p>

0 голосов
/ 08 марта 2012

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...