ทำไมแฮชอินฟินิตี้ของ Python จึงมีตัวเลขเป็นπ


241

แฮชของอนันต์ใน Python มีตัวเลขที่ตรงกับpi :

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

นั่นเป็นเพียงเรื่องบังเอิญหรือเป็นเจตนาหรือไม่?


9
ไม่แน่ใจ แต่คาดเดาของฉันจะเป็นว่ามันเป็นเจตนาเป็นถูกhash(float('nan')) 0
cs95

1
sys.hash_infoอืมไม่มีการพูดถึงเกี่ยวกับว่าใน ไข่อีสเตอร์?
Wim

123
ถามทิมปีเตอร์ส นี่คือการกระทำที่เขาแนะนำอย่างต่อเนื่องนี้ 19 ปีที่ผ่านมา: github.com/python/cpython/commit/... ฉันเก็บค่าพิเศษเหล่านั้นไว้เมื่อทำการแฮชตัวเลขใหม่ในbugs.python.org/issue8188
Mark Dickinson

8
@ MarkDickinson ขอบคุณ ดูเหมือนว่า Tim อาจใช้ตัวเลขของeเพื่อแฮชของ -inf ดั้งเดิม
Wim

17
@wim Ah ใช่จริง -314159และเห็นได้ชัดว่าผมเปลี่ยนไป ฉันลืมไปแล้ว
Mark Dickinson

คำตอบ:


47

_PyHASH_INFถูกกำหนดให้เป็นค่าคงที่เท่ากับ314159เท่ากับ

ฉันไม่พบการสนทนาใด ๆ เกี่ยวกับสิ่งนี้หรือความคิดเห็นที่ให้เหตุผล ฉันคิดว่ามันถูกเลือกโดยพลการมากหรือน้อย ฉันคิดว่าตราบใดที่พวกเขาไม่ได้ใช้คุณค่าที่มีความหมายเหมือนกันสำหรับแฮชอื่น ๆ มันก็ไม่สำคัญ


6
Nitpick ขนาดเล็ก: มันเกือบจะหลีกเลี่ยงไม่ได้โดยนิยามว่าค่าเดียวกันนี้จะใช้สำหรับแฮชอื่น ๆ เช่นในกรณีhash(314159)นี้ก็เช่น314159กัน ลองด้วยใน Python 3 hash(2305843009214008110) == 314159(อินพุตนี้314159 + sys.hash_info.modulus) เป็นต้น
ShreevatsaR

3
@ShreevatsaR ฉันแค่หมายความว่าตราบใดที่พวกเขาไม่ได้เลือกค่านี้เป็นค่าแฮชของค่าอื่น ๆ ตามคำนิยามจากนั้นการเลือกค่าที่มีความหมายเช่นนี้จะไม่เพิ่มโอกาสในการชนกันของ
Patrick Haugh

220

สรุป: มันไม่ใช่เรื่องบังเอิญ _PyHASH_INFเป็น hardcoded เป็น 314,159ในการเริ่มต้นการดำเนินการ CPython ของงูใหญ่และได้รับเลือกเป็นค่าโดยพลการ (ชัดจากตัวเลขของπ) ที่โดยทิมปีเตอร์สในปี 2000


ค่าของการhash(float('inf'))เป็นหนึ่งในพารามิเตอร์ขึ้นอยู่กับระบบของฟังก์ชั่นแฮสำหรับชนิดของตัวเลขและนอกจากนี้ยังมีเป็นsys.hash_info.infในหลาม 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ในบริบทนี้ในรหัสไพ ธ อน (คุณสามารถค้นหาสิ่งนี้ด้วยgit bisectหรือgit log -S 314159 -p) ถูกเพิ่มโดยTim Petersในเดือนสิงหาคม 2000 ในสิ่งที่กระทำ39dce293ในตอนนี้cpythonเก็บ git

ข้อความยืนยันว่า:

แก้ไขสำหรับhttp://sourceforge.net/bugs/?func=detailbug&bug_id=111866&group_id=5470 นี่เป็นข้อผิดพลาดที่ทำให้เข้าใจผิด - "ข้อผิดพลาด" ที่แท้จริงคือการhash(x)ให้ข้อผิดพลาดที่ส่งคืนเมื่อxไม่มีที่สิ้นสุด แก้ไขที่ ที่เพิ่มเข้ามาใหม่แมโครPy_IS_INFINITY pyport.hจัดเรียงโค้ดใหม่เพื่อลดความซ้ำซ้อนที่เพิ่มขึ้นในการแปลงข้อมูลจำนวนลอยและจำนวนเชิงซ้อนผลักดันแทงก่อนหน้าของเทรนต์ไปสู่ข้อสรุปเชิงตรรกะ แก้ไขข้อผิดพลาดที่หายากมากซึ่งการลอยตัวของลอยสามารถคืนค่า -1 แม้ว่าจะไม่มีข้อผิดพลาด (ไม่เสียเวลาในการสร้างกรณีทดสอบมันชัดเจนจากโค้ดที่สามารถเกิดขึ้นได้) ปรับปรุงแฮชที่ซับซ้อนเพื่อให้ hash(complex(x, y))ไม่ให้มีระบบที่เท่าเทียมกันhash(complex(y, x))อีกต่อไป

โดยเฉพาะอย่างยิ่งในเรื่องนี้กระทำเขาฉีกออกรหัสของ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 เกิดขึ้นจากตัวเลขทศนิยมสองสามตัวแรกของeอี

เกี่ยวข้องในภายหลังกระทำ:


44
ทางเลือกของ -271828 สำหรับ -Inf ช่วยลดข้อสงสัยว่าสมาคม pi นั้นเป็นอุบัติเหตุ
รัสเซล Borogove

24
@RussellBorogove ไม่ แต่มันทำให้มีโอกาสน้อยกว่าหนึ่งล้านครั้ง)
pipe

8
@cmaster: ดูส่วนข้างต้นที่จะกล่าวว่าเดือนพฤษภาคม 2010 คือเอกสารประกอบในคร่ำเครียดประเภทที่เป็นตัวเลขและปัญหา 8188 - ความคิดที่ว่าเราต้องการhash(42.0)ที่จะเป็นเช่นเดียวกับhash(42)ยังเป็นเช่นเดียวกับhash(Decimal(42))และและhash(complex(42)) hash(Fraction(42, 1))การแก้ปัญหา (โดย Mark Dickinson) เป็น IMO ที่สง่างาม: การกำหนดฟังก์ชั่นทางคณิตศาสตร์ที่ใช้งานได้กับจำนวนตรรกยะใด ๆ และการใช้ความจริงที่ว่าจำนวนจุดลอยตัวเป็นจำนวนตรรกยะเช่นกัน
ShreevatsaR

1
@ShreevatsaR อ่าขอบคุณ ในขณะที่ฉันจะไม่ได้รับการดูแลที่จะรับประกัน equalities เหล่านี้เป็นเรื่องที่ดีที่จะรู้ว่ามีความดีเป็นของแข็งและคำอธิบายเหตุผลสำหรับรหัสที่ซับซ้อนดูเหมือน :-)
cmaster - คืนสิทธิ์ monica

2
@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) เหตุผลที่เราต้องการ: ถ้าเป็น dict ที่dเราใส่d[x] = fooและจากนั้นเรามีx==y(เช่น 42.0 == 42) แต่d[y]ไม่เหมือนกันd[x]แล้วเราจะมีปัญหา รหัสที่ซับซ้อนที่ดูเหมือนจะเกิดขึ้นส่วนใหญ่มาจากธรรมชาติของรูปแบบจุดลอยตัวเองเพื่อกู้คืนเศษส่วนได้อย่างถูกต้องและต้องการกรณีพิเศษสำหรับค่า inf และ NaN
ShreevatsaR

12

อันที่จริง

sys.hash_info.inf

314159ผลตอบแทน ค่าไม่ถูกสร้างขึ้นมันสร้างไว้ในซอร์สโค้ด ในความเป็นจริง,

hash(float('-inf'))

ส่งคืน-271828หรือประมาณ -e ใน python 2 ( ตอนนี้คือ -314159 )

ความจริงที่ว่าตัวเลขไม่ลงตัวที่มีชื่อเสียงที่สุดของเวลาทั้งหมดถูกนำมาใช้เป็นค่าแฮชทำให้ไม่น่าจะเป็นเรื่องบังเอิญ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.