Равенство с плавающей точкой для кеширования дорогих вычислений

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

Представьте, что у вас есть этот код:

if(myfloat != _last_float) {
refresh_expensive_computation(myfloat);
_last_float = myfloat;
}

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

В случае, если они действительно равны (то есть, если бы мы могли вычислять с помощью вещественных чисел, а не с плавающей запятой), но ошибочно определяли, что это не так, в худшем случае мы выполняем дорогостоящие вычисления с избыточностью, но ответ нашей программы по-прежнему верен. AFAIK, они могут только ошибочно сравнивать равные, если вычисление было выполнено в регистре, который шире, чем представление памяти с плавающей запятой (например, на 32-битном x86, когда включены 80-битные регистры fp), и после преобразования в представление памяти они происходят оба будут поразрядно равны. В этом случае разница должна быть выше точности представления памяти, которая должна быть ниже эпсилона для сравнений, которые важны для меня, потому что в противном случае я бы использовал более широкий тип, такой как double.

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

Во-вторых, если мы предположим, что это безопасно, я бы хотел избежать ошибочного возврата true, потому что это вызывает дорогостоящие вычисления. Одним из способов избежать этого на машинах с более широкими регистрами, чем представления памяти, было бы использование memcmp, чтобы заставить его сравнивать представления памяти (семантика не будет точно такой же для NaN, которая теперь будет сравнивать true с точно идентичным побитовым экземпляром сам по себе, но для кеширования это улучшение, или для +0 и -0, но это может быть особый случай). Однако этот memcmp будет медленнее, чем сравнение с плавающей запятой в регистрах. Есть ли способ определить, когда платформа имеет более широкие регистры, поэтому я могу использовать #ifdef или аналогичный, чтобы получить оптимизированную реализацию на платформах, где это безопасно?

2

Решение

Наиболее memcmp Реализации имеют небольшие значения оптимизации для регистров, так что это должно быть хорошо, чтобы использовать это. Однако, если вы не хотите полагаться на это, вы можете сделать что-то вроде reinterpret_cast<int>(), Добавить compile_assert(sizeof(int) == sizeof(float)) если вы хотите быть более безопасным и используете набор библиотек, который включает в себя такую ​​команду.

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

2

Другие решения

(C99) Чтобы избежать математики FP с более высокой точностью от предоставления не точный сравнить, использовать volatile заставить вычисление использовать самые последние поплавок ценности.

if ((volatile float) myfloat != (volatile float) _last_float) {
refresh_expensive_computation(myfloat);
_last_float = myfloat;
}

Примечание: использование_ в качестве ведущего символа, а затем буква в качестве имени переменной зарезервирована. Лучше переименовать _last_float,

Примечание: -0.0f равно + 0.0f. Если эти разные floats, имеющие одинаковое значение, важны, необходим другой код, чем !=,

0

По вопросам рекламы [email protected]