Функция для определения, если два числа почти равны при округлении до n значащих десятичных цифр - PullRequest
30 голосов
/ 17 февраля 2009

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

def nearlyequal( a, b, sigfig=5 ):

Цель этой функции - определить, приблизительно ли два числа с плавающей точкой (a и b) приблизительно равны. Функция вернет True, если a == b (точное совпадение) или a и b имеют одинаковое значение при округлении до sigfig значащих цифр при записи в десятичном виде.

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

assert nearlyequal(1, 1, 5) 
assert nearlyequal(1.0, 1.0, 5) 
assert nearlyequal(1.0, 1.0, 5) 
assert nearlyequal(-1e-9, 1e-9, 5) 
assert nearlyequal(1e9, 1e9 + 1 , 5) 
assert not nearlyequal( 1e4, 1e4 + 1, 5) 
assert nearlyequal( 0.0, 1e-15, 5 ) 
assert not nearlyequal( 0.0, 1e-4, 6 ) 

Дополнительные примечания:

  1. Значения a и b могут иметь тип int, float или numpy.float64. Значения a и b всегда будут одного типа. Важно, чтобы преобразование не вносило дополнительную ошибку в функцию.
  2. Позволяет сохранить это число, поэтому функции, которые преобразуются в строки или используют нематематические приемы, не идеальны. Эта программа будет проверена кем-то, кто является математиком, который захочет доказать, что функция выполняет то, что должна.
  3. Скорость ... Я должен сравнить множество чисел, поэтому чем быстрее, тем лучше.
  4. У меня есть тупица, сципи и стандартная библиотека. Мне будет трудно получить что-то еще, особенно для такой маленькой части проекта.

Ответы [ 11 ]

56 голосов
/ 04 апреля 2016

Начиная с Python 3.5, стандартный способ сделать это (используя стандартную библиотеку) с помощью функции math.isclose.

Имеет следующую подпись:

isclose(a, b, rel_tol=1e-9, abs_tol=0.0)

Пример использования с абсолютной погрешностью:

from math import isclose
a = 1.0
b = 1.00000001
assert isclose(a, b, abs_tol=1e-8)

Если вы хотите получить его с точностью n значащих цифр, просто замените последнюю строку на:

assert isclose(a, b, abs_tol=10**-n)
20 голосов
/ 17 февраля 2009

Существует функция assert_approx_equal в numpy.testing (источник здесь) , которая может быть хорошей отправной точкой.

def assert_approx_equal(actual,desired,significant=7,err_msg='',verbose=True):
    """
    Raise an assertion if two items are not equal up to significant digits.

    .. note:: It is recommended to use one of `assert_allclose`,
              `assert_array_almost_equal_nulp` or `assert_array_max_ulp`
              instead of this function for more consistent floating point
              comparisons.

    Given two numbers, check that they are approximately equal.
    Approximately equal is defined as the number of significant digits
    that agree.
8 голосов
/ 17 февраля 2009

Вот дубль.

def nearly_equal(a,b,sig_fig=5):
    return ( a==b or 
             int(a*10**sig_fig) == int(b*10**sig_fig)
           )
4 голосов
/ 17 февраля 2009

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

Если под «округлением до N десятичных знаков сиг-фи» вы подразумеваете «N десятичных знаков справа от десятичной запятой», то проверка assert nearlyequal(1e9, 1e9 + 1 , 5) не будет выполнена, потому что даже при округлении 1000000000 и 1000000001 с точностью 0,00001 , они все еще разные.

И если под «округлением до N десятичных знаков сиг-фига» вы подразумеваете «N старших значащих цифр, независимо от десятичной точки», то тест assert nearlyequal(-1e-9, 1e-9, 5) должен провалиться, потому что 0,000000001 и -0,000000001 полностью отличаются, смотреть таким образом.

Если вы имели в виду первое определение, то первый ответ на этой странице (Триптих) хорош. Если вы имели в виду второе определение, пожалуйста, скажите это, я обещаю подумать об этом: -)

3 голосов
/ 19 февраля 2009

Уже есть много отличных ответов, но вот мысль:

def closeness(a, b):
  """Returns measure of equality (for two floats), in unit
     of decimal significant figures."""
  if a == b:
    return float("infinity")
  difference = abs(a - b)
  avg = (a + b)/2
  return math.log10( avg / difference )


if closeness(1000, 1000.1) > 3:
  print "Joy!"
2 голосов
/ 17 февраля 2009

«Значимые цифры» в десятичном формате - это вопрос корректировки десятичной точки и усечения до целого числа.

>>> int(3.1415926 * 10**3)
3141
>>> int(1234567 * 10**-3)
1234
>>>
1 голос
/ 20 октября 2009

Есть интересное решение этой проблемы Б. Доусона (с кодом C ++) в «Сравнение чисел с плавающей точкой» . Его подход основывается на строгом IEEE представлении двух чисел и принудительном лексикографическом порядке, когда указанные числа представлены в виде целых чисел без знака.

1 голос
/ 19 февраля 2009

Орен Шемеш получил часть проблемы с проблемой, как указано, но есть еще:

утверждают почти равными (0,0, 1e-15, 5)

также не соответствует второму определению (и это определение я выучил в школе).

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

1 голос
/ 19 февраля 2009

Это довольно распространенная проблема с числами с плавающей запятой. Я решаю это на основе обсуждения в Разделе 1.5 Деммеля [1]. (1) Рассчитать ошибку округления. (2) Убедитесь, что ошибка округления меньше некоторого эпсилона. Некоторое время я не использовал python и имел только версию 2.4.3, но постараюсь исправить это.

Шаг 1. Ошибка округления

def roundoff_error(exact, approximate):
    return abs(approximate/exact - 1.0)

Шаг 2. Равенство с плавающей точкой

def float_equal(float1, float2, epsilon=2.0e-9):
    return (roundoff_error(float1, float2) < epsilon)

У этого кода есть несколько явных недостатков.

  1. Ошибка деления на ноль, если точное значение равно нулю.
  2. Не проверяет, что аргументы являются значениями с плавающей запятой.

Редакция 1.

def roundoff_error(exact, approximate):
    if (exact == 0.0 or approximate == 0.0):
        return abs(exact + approximate)
    else:
        return abs(approximate/exact - 1.0)

def float_equal(float1, float2, epsilon=2.0e-9):
    if not isinstance(float1,float):
        raise TypeError,"First argument is not a float."
    elif not isinstance(float2,float):
        raise TypeError,"Second argument is not a float."
    else:
        return (roundoff_error(float1, float2) < epsilon)

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

На данный момент единственной трудной вещью является установка правильного значения для эпсилона. В документации для версии 2.6.1 я заметил, что в sys.float_info есть атрибут epsilon, поэтому я бы использовал это значение в два раза больше, чем значение по умолчанию epsilon. Но правильное значение зависит как от вашего приложения, так и от вашего алгоритма.

[1] Джеймс В. Деммель, Прикладная числовая линейная алгебра , SIAM, 1997.

0 голосов
/ 22 августа 2018

Меня попросили проверить библиотеку, предоставленную третьей стороной

Если вы используете Python по умолчанию unittest framework , вы можете использовать assertAlmostEqual

self.assertAlmostEqual(a, b, places=5)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...