Почему в хэше бесконечности Python есть цифры π?

Хэш бесконечности в Python имеет цифры, соответствующие Пи:

>>> inf = float('inf')
>>> hash(inf)
314159
>>> int(math.pi*1e5)
314159

Это просто совпадение или это намеренно?

Не уверен, но я предполагаю, что это так же преднамеренно, как hash(float('nan')) быть 0.

cs95 20.05.2019 22:04

Хм, об этом не упоминается в sys.hash_info. Пасхальное яйцо?

wim 20.05.2019 22:09

Кажется, однажды кто-то был в легкомысленном настроении. Но почему нет?

John Coleman 20.05.2019 22:11

Спросите Тима Питерса. Вот коммит, в котором он представил эту константу 19 лет назад: github.com/python/cpython/commit/…. Я сохранил эти специальные значения, когда перерабатывал числовой хэш в bugs.python.org/issue8188.

Mark Dickinson 20.05.2019 22:38

@MarkDickinson Спасибо. Похоже, что Тим, возможно, также использовал цифры е для хэша -inf.

wim 20.05.2019 22:42

@wim Ах да, правда. И, видимо, я изменил это на -314159. Я забыл об этом.

Mark Dickinson 20.05.2019 22:44
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
241
6
28 811
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

_PyHASH_INF равно определяется как константа314159.

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

Небольшая придирка: почти неизбежно по определению, что одно и то же значение будет использоваться для других хэшей, например. в этом случае hash(314159) также 314159. Также попробуйте в Python 3 hash(2305843009214008110) == 314159 (это ввод 314159 + sys.hash_info.modulus) и т. д.

ShreevatsaR 21.05.2019 13:43

@ShreevatsaR Я просто имел в виду, что до тех пор, пока они не выбирают это значение как хэш других значений по определению, выбор такого значимого значения не увеличивает вероятность коллизий хэшей.

Patrick Haugh 21.05.2019 15:37

Резюме: Это не совпадение; _PyHASH_INF жестко запрограммирован как 314159 в реализации Python по умолчанию на CPython и было выбрано как произвольное значение (очевидно, из цифр π) Тим Питерс, 2000 г..


Значение hash(float('inf')) является одним из системно-зависимых параметров встроенной хеш-функции для числовых типов, а также доступен как sys.hash_info.inf в Python 3:

>>> import sys
>>> sys.hash_info
sys.hash_info(width=64, modulus=2305843009213693951, inf=314159, nan=0, imag=1000003, algorithm='siphash24', hash_bits=64, seed_bits=128, cutoff=0)
>>> sys.hash_info.inf
314159

(Те же результаты с PyPy тоже.)


С точки зрения кода, hash — это встроенная функция. Вызов его для объекта с плавающей запятой Python вызывает функцию, указатель которой задается tp_hash атрибут встроенного типа с плавающей запятой (PyTypeObject PyFloat_Type), где является является функцией float_hash, определенный как return _Py_HashDouble(v->ob_fval), который, в свою очередь, имеет

    if (Py_IS_INFINITY(v))
        return v > 0 ? _PyHASH_INF : -_PyHASH_INF;

где _PyHASH_INFопределяется как 314159:

#define _PyHASH_INF 314159

С точки зрения истории, первое упоминание 314159 в этом контексте в коде Python (вы можете найти это с помощью git bisect или git log -S 314159 -p) было добавлено Тим Питерс в августе 2000 года, в том, что сейчас является коммитом 39dce293 в cpython репозитории git.

В сообщении коммита говорится:

Fix for http://sourceforge.net/bugs/?func=detailbug&bug_id=111866&group_id=5470. This was a misleading bug -- the true "bug" was that hash(x) gave an error return when x is an infinity. Fixed that. Added new Py_IS_INFINITY macro to pyport.h. Rearranged code to reduce growing duplication in hashing of float and complex numbers, pushing Trent's earlier stab at that to a logical conclusion. Fixed exceedingly rare bug where hashing of floats could return -1 even if there wasn't an error (didn't waste time trying to construct a test case, it was simply obvious from the code that it could happen). Improved complex hash so that hash(complex(x, y)) doesn't systematically equal hash(complex(y, x)) anymore.

В частности, в этом коммите он выдрал код static long float_hash(PyFloatObject *v) в Objects/floatobject.c и сделал просто return _Py_HashDouble(v->ob_fval);, а в определение long _Py_HashDouble(double v) в Objects/object.c добавил строчки:

        if (Py_IS_INFINITY(intpart))
            /* can't convert to long int -- arbitrary */
            v = v < 0 ? -271828.0 : 314159.0;

Как уже упоминалось, это был произвольный выбор. Обратите внимание, что 271828 формируется из первых нескольких десятичных цифр е.

Связанные более поздние коммиты:

Выбор -271828 для -Inf устраняет любые сомнения в том, что ассоциация pi была случайной.

Russell Borogove 21.05.2019 06:30

@RussellBorogove Нет, но это делает это примерно в миллион раз менее вероятным;)

pipe 21.05.2019 17:01

@cmaster: см. часть выше, где говорится о мае 2010 года, а именно раздел документации по хеширование числовых типов и выпуск 8188 — идея в том, что мы хотим, чтобы hash(42.0) было таким же, как hash(42), а также таким же, как hash(Decimal(42)), hash(complex(42)) и hash(Fraction(42, 1)). Решение (от Марка Дикинсона) является элегантным IMO: определение математической функции, которая работает для любого рационального числа, и использование того факта, что числа с плавающей запятой также являются рациональными числами.

ShreevatsaR 22.05.2019 15:22

@ShreevatsaR Ах, спасибо. Хотя я бы не стал гарантировать эти равенства, приятно знать, что есть хорошее, надежное и логичное объяснение кажущемуся сложным коду :-)

cmaster - reinstate monica 22.05.2019 16:00

@cmaster На самом деле это необходимость. Хэшируемые объекты, которые сравниваются равными, должны иметь одинаковое хеш-значение.

wim 23.05.2019 00:51

@cmaster Хэш-функция для целых чисел — это просто hash(n) = n % M, где M = (2 ^ 61 - 1). Это обобщается для рационального n на hash(p/q) = (p/q) mod M с интерпретацией деления по модулю M (другими словами: hash(p/q) = (p * inverse(q, M)) % M). Причина, по которой мы хотим этого: если в диктофон d мы поместим d[x] = foo, а затем получим x==y (например, 42,0 == 42), но d[y] не то же самое, что d[x], тогда у нас будет проблема. Большая часть кажущегося сложным кода возникает из-за самой природы формата с плавающей запятой, для правильного восстановления дроби и необходимости специальных случаев для значений inf и NaN.

ShreevatsaR 23.05.2019 01:22

Конечно,

sys.hash_info.inf

возвращается 314159. Значение не генерируется, оно встроено в исходный код. Фактически,

hash(float('-inf'))

возвращает -271828 или приблизительно -e в python 2 (сейчас -314159).

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

Другие вопросы по теме