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