Как добиться согласованного поведения программы при использовании чисел с плавающей запятой?

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

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

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(), на который не будут влиять крошечные вариации результатов вычислений, возникающие в результате переупорядочения операций, чтобы поведение более новые версии моей программы будут согласовываться со старыми версиями.

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

if (value < epsilon) something_happens();

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

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

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


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

11
задан sjs 8 March 2012 в 06:07
поделиться