Я пишу программу моделирования, которая проходит в дискретных шагах. Моделирование состоит из множества узлов, каждое из которых имеет связанное с ним значение с плавающей запятой, которое пересчитывается на каждом шаге. Результат может быть положительным, отрицательным или нулевым.
В случае, когда результат равен нулю или меньше, что-то происходит. Пока это кажется простым - я могу сделать что-то подобное для каждого node:
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 выходило отрицательно, запускающее something_happens(), теперь может быть отрицательным шагом или двумя раньше или позже, что может привести к очень разным общим результатам моделирования, поскольку something_happens() имеет большой эффект.
Я хочу знать, есть ли хороший способ решить, когда нужно вызвать something_happens(), на которое не будут влиять крошечные вариации результатов расчета, которые возникают в результате операций переопределения, так что поведение более новые версии моей программы будут соответствовать более старым версиям.
Единственным решением, о котором я до сих пор мог подумать, является использование некоторого значения epsilon следующим образом:
if (value < epsilon) something_happens();
но поскольку крошечные вариации в результатах накапливаются со временем, мне нужно сделать epsilon довольно большим (относительно говоря), чтобы гарантировать, что изменения не приведут к срабатыванию something_happens() на другом шаге. Есть ли лучший способ?
Я прочитал эту замечательную статью о сравнении с плавающей запятой, но я не вижу, как какой-либо из описанных методов сравнения может мне помочь в этой ситуации.
Примечание. Вместо этого использование целочисленных значений не является опцией.
Изменить была увеличена возможность использования удвоений вместо поплавков. Это не решило бы мою проблему, так как вариации все равно были бы там, они были бы только меньшей величины.