Меня немного смущает точность чисел с плавающей запятой в Python и проверки равенства.
например.,
15 + 1e-16 == 15
оценивается как истинное, поскольку 1e-16 <машинный эпсилон для fp64.
и
15 + 1e-15 == 15
оценивает ложь, потому что 1e-15 > машинный эпсилон для fp64.
Но когда я делаю sys.getsizeof(15 + 1e-16)
, я получаю 24 байта, то есть fp192, что, если бы это было правдой, означало бы, что 15 + 1e-16 == 15
будет оценивать ложь, поскольку машинный эпсилон для fp192 наверняка << 1e-16.
Я всегда думал, что ==
проверяет, находится ли левая часть тела в пределах +/-
эпсилона машины соответствующего типа.
Чтобы еще больше усугубить мое замешательство, я проверил:
1e-323 == 0 # evaluates false
1e-324 == 0 # evaluates true
sys.getsizeof(1e-323) # gives 24 bytes
Я не понимаю ограничений в этом.
Кроме того, «машинный эпсилон» — это абсолютная точность около 1,0. Для чисел около 16 эффективный эпсилон будет в 8 или 16 раз больше.
@SteveSummit «реализовать == как строгое равенство», ах, я понимаю, но строгое равенство — это просто проверка того, что побитовое равенство дает равенство, верно? Я действительно думал, что это эквивалентно проверке допусков на эпсилон?
То, что сравнивается побитово, — это значение двойной точности IEEE754 (все его 64 бита), и сравнение проводится для точного равенства. То, что вы набрали, вообще не сравнивается — на ранней стадии интерпретатор Python переводит символы 1e-324
в ближайшее значение двойной точности IEEE754, и именно это значение в конечном итоге сравнивается. И оказывается, что в случае 1 e - 3 2 4
ближайшее значение двойной точности IEEE754 равно 0,0.
«Строгое равенство — это просто проверка того, что побитовое равенство дает равенство, верно?» --> Нет. -0.0 == +0.0 верно, но имеет разные битовые комбинации. a == a
обычно является ложным, когда a
не является числом, но a
и a
имеют одинаковую битовую комбинацию.
Сравнение всех числовых типов (включая числа с плавающей запятой) в Python является точным. Точность оборудования здесь ни при чем.
getsizeof()
возвращает размер объекта Python, который включает в себя такие вещи, как байты для указателя на объект типа объекта и счетчик ссылок. Фактические данные для числа с плавающей запятой занимают 8 байт практически на всех современных машинах, лишь небольшую часть.
Когда вы говорите «точный», вы имеете в виду точный в битовом представлении, а не в математическом смысле?
Оба. Сравнение видит только битовые представления. Это арифметические операции (+
, -
, *
, ...), которые могут потерять информацию из-за округления. Сравнения принимают исходные данные за чистую монету и выполняют точное сравнение предполагаемых точных сравнений.
Понятно, а как насчет 1e-324 == 0
? Очевидно, что это не математическая эквивалентность, а эквивалент в коде из-за ограничений аппаратной точности?
Сравнение равенства по-прежнему является точным - оно всегда точно ;-) Это литерал 1e-324
сам по себе, изолированно, который теряет информацию из-за округления. Оно слишком мало для представления вашим аппаратным обеспечением, поэтому округляется ровно до 0, ближайшего числа, которое может представлять ваше аппаратное обеспечение.
И если это не ясно, то происходит следующее: строка 1e-324
, которую вы вводите в своей программе на Python, более или менее сразу преобразуется в 0,0, а затем 0,0 становится равным 0. То есть при анализе вашего кода 1e-324
преобразуется в 0, а позже, при вычислении выражения, оценщику выражения предлагается сравнить 0,0 и 0,0.
@SteveSummit Верно, я понимаю, что оно преобразуется в 0. Пробел в моем понимании заключается в том, что я не знал числового порога, при котором значение преобразуется в 0. Сколько себя помню, я всегда думал, что числовой порог равен машинный eps (~2.2e-16 для двойной точности IIRC), но теперь я вижу, что на самом деле не имею к этому никакого отношения
~2,2e-16 — это наименьшая степень 2, которая при добавлении к 1,0 не полностью теряется при ближайшем/четном округлении. Он равен math.ulp(1.0)
, весу степени 2 наименее значимого представимого бита 1,0. 1.0 + math.ulp(1.0)
больше 1, но 1.0 + math.ulp(1.0) / 2
ровно 1,0.`
Итак, 2,2e-16 — это абсолютная точность в диапазоне (бинаде) от 1,0 до 2. От 2,0 до 4 — это вдвое больше. От 0,5 до 1 — это вдвое меньше. А вблизи минимального значения двойной точности (именно это нас здесь волнует) оно составляет 2⁻¹⁰²², или около 5e-324. Вот почему 1e-323 заметно отличался от 0, а 1e-324 — нет. (И, следовательно, эпсилон действительно имеет к этому отношение, если вы правильно на него смотрите, то есть масштабируете.)
Во-первых,
sys.getsizeof
дает размер объекта в памяти, который может включать в себя некоторые накладные расходы; это не то же самое, что, скажем,sizeof
в C. Таким образом, размер 24 не означает, что вы на самом деле имеете дело со 192-битной плавающей запятой повышенной точности. И затем, к вашему главному вопросу, большинство языков (включая Python) реализуют==
как строгое равенство. Если вам нужен примерный тест на равенство, вы часто можете действовать самостоятельно. (Но см. peps.python.org/pep-0485 .)