F# — как сравнивать числа с плавающей запятой

В фа#. Как эффективно сравнивать поплавки на равенство, которые почти равны? Он должен работать и для очень больших, и для очень маленьких значений. Я думаю сначала сравнить экспоненту, а затем мантиссу (мантисса), игнорируя последние 4 бита из 52 бит. Это хороший подход? Как я могу получить показатель степени и значимость числа с плавающей запятой?


person Goswin    schedule 11.06.2013    source источник
comment
Что не так с чем-то вроде abs(a-b)<threshold. Или abs(a-b)<threshold*a. Получение битов с плавающей запятой в F #, вероятно, будет иметь больше накладных расходов, чем выполнение любого из них, поскольку для этого потребуется выделить массив байтов.   -  person John Palmer    schedule 11.06.2013
comment
Спасибо, @JohnPalmer Ваше второе предложение abs(a-b)<threshold*abs(a) (с дополнительным прессом()) - это то, что я искал. относительный порог.   -  person Goswin    schedule 11.06.2013
comment
Обсуждение на stackoverflow.com/a/77735/103167 должно помочь вам выбрать правило, хотя оно не касается написания это правило в F#.   -  person Ben Voigt    schedule 07.07.2019


Ответы (2)


F# float — это просто сокращение от System.Double. В этом случае вы можете использовать BitConverter.DoubleToInt64Bits метод для эффективного (и безопасного!) "преобразования" значения F# float в int64; это полезно, потому что позволяет избежать выделения byte[], как упомянул Джон в своем комментарии. Вы можете получить показатель степени и мантиссу из этого int64, используя несколько простых побитовых операций.

Однако, как сказал Джон, вам, вероятно, будет лучше с простой проверкой относительной точности. Вероятно, это будет самое быстрое решение и «достаточно близкое» для многих случаев использования (например, проверка того, сошелся ли итеративный решатель с решением). Если вам нужна конкретная степень точности, взгляните на код NUnit — у него есть несколько хороших API-интерфейсов для подтверждения того, что значения находятся в пределах определенного процента или количества ulps от ожидаемого значения.

person Jack P.    schedule 11.06.2013

Когда вы спрашиваете, как сравнивать значения с плавающей запятой, которые почти равны, вы спрашиваете:

  • У меня есть два значения, x и y, которые были вычислены с помощью арифметики с плавающей запятой, поэтому они содержат ошибки округления и являются приближениями к идеальным математическим значениям x и y. Как я могу использовать x и y с плавающей запятой для сравнения математических x и y на равенство?

Здесь есть две проблемы:

  1. Мы не знаем, сколько ошибок может быть в x или y. Некоторые комбинации арифметических операций увеличивают количество ошибок, а другие уменьшают их. Ошибки в x и y могут варьироваться от нуля до бесконечности, и вы не предоставили нам никакой информации об этом.
  2. Часто предполагается, что цель состоит в том, чтобы получить результат «равно», когда x и y не равны, но близки друг к другу. Это преобразует ложноотрицательные результаты (о неравенстве сообщается, даже если математические x и y равны) в положительные. Однако это создает ложные срабатывания (сообщается о равенстве, даже если математические x и y не равны).

Для этих проблем нет общего решения.

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

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

Обратите внимание, что эта проблема на самом деле не связана с проверкой на равенство. Как правило, невозможно вычислить любую функцию неверных данных (за исключением тривиальных функций, таких как функции-константы). Поскольку x и y содержат ошибки, невозможно использовать x для вычисления log(x) без ошибок или для вычисления аркосинуса(y) или sqrt(x ) без ошибок. На самом деле, если ошибки сделали y чуть больше 1, а y нет, или сделали x немного меньше нуля, а x нет, то вычисление acos(y) или sqrt(x) приведет к исключениям и NaN, даже если в идеале математические значения будут работать без проблем.

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

person Eric Postpischil    schedule 11.06.2013