ความคิดเห็นในซอร์สโค้ดของ Python สำหรับวัตถุลอยยอมรับว่า:
การเปรียบเทียบค่อนข้างเป็นฝันร้าย
นี่เป็นความจริงโดยเฉพาะอย่างยิ่งเมื่อเปรียบเทียบการลอยกับจำนวนเต็มเพราะต่างจากการลอยจำนวนเต็มใน Python สามารถใหญ่โดยพลการและแน่นอนเสมอ ความพยายามในการแปลงจำนวนเต็มไปลอยอาจทำให้ความแม่นยำลดลงและทำการเปรียบเทียบที่ไม่ถูกต้อง ความพยายามในการร่ายลอยให้เป็นจำนวนเต็มจะไม่ได้ผลเช่นกันเพราะส่วนที่เป็นเศษส่วนจะหายไป
เพื่อแก้ไขปัญหานี้ Python ดำเนินการตรวจสอบหลายชุดโดยส่งคืนผลลัพธ์หากการตรวจสอบหนึ่งประสบความสำเร็จ มันเปรียบเทียบสัญญาณของทั้งสองค่าจากนั้นไม่ว่าจะเป็นจำนวนเต็ม "ใหญ่เกินไป" ที่จะลอยแล้วเปรียบเทียบตัวแทนของลอยไปที่ความยาวของจำนวนเต็ม หากทุกอย่างของการตรวจสอบเหล่านี้ล้มเหลวก็เป็นสิ่งจำเป็นในการสร้างวัตถุสองหลามใหม่ในการเปรียบเทียบเพื่อให้ได้ผล
เมื่อเปรียบเทียบการลอยv
กับจำนวนเต็ม / ยาวw
กรณีที่แย่ที่สุดคือ:
v
และw
มีเครื่องหมายเดียวกัน (ทั้งบวกหรือลบทั้งสอง)
- จำนวนเต็ม
w
มีบิตเพียงพอเล็กน้อยที่สามารถเก็บไว้ในsize_t
ประเภท (โดยทั่วไปคือ 32 หรือ 64 บิต)
- จำนวนเต็ม
w
มีอย่างน้อย 49 บิต
- ตัวแทนของลอยเป็นเช่นเดียวกับจำนวนบิตใน
v
w
และนี่คือสิ่งที่เรามีสำหรับค่าในคำถาม:
>>> import math
>>> math.frexp(562949953420000.7) # gives the float's (significand, exponent) pair
(0.9999999999976706, 49)
>>> (562949953421000).bit_length()
49
เราจะเห็นว่า 49 เป็นทั้งตัวแทนของลอยและจำนวนบิตในจำนวนเต็มที่ ตัวเลขทั้งสองเป็นค่าบวกและตรงตามเกณฑ์ทั้งสี่ด้านบน
การเลือกหนึ่งในค่าที่จะใหญ่กว่า (หรือเล็กกว่า) สามารถเปลี่ยนจำนวนบิตของจำนวนเต็มหรือค่าของเลขชี้กำลังได้ดังนั้น Python จึงสามารถกำหนดผลลัพธ์ของการเปรียบเทียบได้โดยไม่ต้องทำการตรวจสอบขั้นสุดท้ายที่มีราคาแพง
นี่เป็นเฉพาะการใช้งาน CPython ของภาษา
การเปรียบเทียบรายละเอียดเพิ่มเติม
float_richcompare
ฟังก์ชั่นจัดการการเปรียบเทียบระหว่างสองค่าและv
w
ด้านล่างนี้เป็นคำอธิบายขั้นตอนโดยขั้นตอนของการตรวจสอบที่มีประสิทธิภาพการทำงาน ความคิดเห็นในแหล่งไพ ธ อนนั้นมีประโยชน์จริง ๆ เมื่อพยายามทำความเข้าใจว่าฟังก์ชันทำอะไรดังนั้นฉันจึงทิ้งมันไว้ในที่ที่เกี่ยวข้อง ฉันได้สรุปการตรวจสอบเหล่านี้ในรายการที่ส่วนท้ายของคำตอบ
แนวคิดหลักคือการแมปวัตถุ Python v
และw
C สองตัวที่เหมาะสมi
และj
ซึ่งสามารถเปรียบเทียบได้ง่ายเพื่อให้ได้ผลลัพธ์ที่ถูกต้อง ทั้ง Python 2 และ Python 3 ใช้แนวคิดเดียวกันในการทำสิ่งนี้ (ก่อนหน้านี้เพิ่งจัดการint
และlong
แยกประเภท)
สิ่งแรกที่ต้องทำคือการตรวจสอบว่าv
เป็นมั่นเหมาะลอยงูหลามและแผนที่ไปยัง C i
คู่ ถัดไปดูฟังก์ชั่นที่ว่ายังเป็นลอยและแผนที่ไปยังคู่w
C j
นี่เป็นสถานการณ์จำลองที่ดีที่สุดสำหรับฟังก์ชันเนื่องจากสามารถข้ามการตรวจสอบอื่นทั้งหมดได้ ฟังก์ชั่นนี้ยังตรวจสอบว่าv
มีinf
หรือnan
:
static PyObject*
float_richcompare(PyObject *v, PyObject *w, int op)
{
double i, j;
int r = 0;
assert(PyFloat_Check(v));
i = PyFloat_AS_DOUBLE(v);
if (PyFloat_Check(w))
j = PyFloat_AS_DOUBLE(w);
else if (!Py_IS_FINITE(i)) {
if (PyLong_Check(w))
j = 0.0;
else
goto Unimplemented;
}
ตอนนี้เรารู้แล้วว่าหากการw
ตรวจสอบเหล่านี้ล้มเหลวมันไม่ใช่ Python float ตอนนี้ฟังก์ชั่นตรวจสอบว่ามันเป็นจำนวนเต็มหลาม หากเป็นกรณีนี้การทดสอบที่ง่ายที่สุดคือการแยกสัญญาณของv
และสัญญาณw
(กลับมา0
ถ้าศูนย์-1
ถ้าลบ1
ถ้าบวก) หากสัญญาณต่างกันนี่คือข้อมูลทั้งหมดที่จำเป็นในการส่งคืนผลลัพธ์ของการเปรียบเทียบ:
else if (PyLong_Check(w)) {
int vsign = i == 0.0 ? 0 : i < 0.0 ? -1 : 1;
int wsign = _PyLong_Sign(w);
size_t nbits;
int exponent;
if (vsign != wsign) {
/* Magnitudes are irrelevant -- the signs alone
* determine the outcome.
*/
i = (double)vsign;
j = (double)wsign;
goto Compare;
}
}
หากการตรวจสอบนี้ล้มเหลวแล้วv
และw
มีสัญญาณเดียวกัน
w
การตรวจสอบต่อไปนับจำนวนบิตในจำนวนเต็ม หากมีจำนวนบิตมากเกินไปก็จะไม่สามารถจัดเป็นแบบลอยได้ดังนั้นจะต้องมีขนาดใหญ่กว่าการลอยv
:
nbits = _PyLong_NumBits(w);
if (nbits == (size_t)-1 && PyErr_Occurred()) {
/* This long is so large that size_t isn't big enough
* to hold the # of bits. Replace with little doubles
* that give the same outcome -- w is so large that
* its magnitude must exceed the magnitude of any
* finite float.
*/
PyErr_Clear();
i = (double)vsign;
assert(wsign != 0);
j = wsign * 2.0;
goto Compare;
}
ในทางกลับกันถ้าจำนวนเต็มw
มี 48 หรือน้อยกว่าบิตมันสามารถเปลี่ยนได้อย่างปลอดภัยใน C คู่j
และเปรียบเทียบ:
if (nbits <= 48) {
j = PyLong_AsDouble(w);
/* It's impossible that <= 48 bits overflowed. */
assert(j != -1.0 || ! PyErr_Occurred());
goto Compare;
}
จากจุดนี้เป็นต้นไปเรารู้ว่าw
มี 49 หรือมากกว่าบิต มันจะสะดวกในการรักษาw
เป็นจำนวนเต็มบวกดังนั้นเปลี่ยนเครื่องหมายและตัวดำเนินการเปรียบเทียบตามความจำเป็น:
if (nbits <= 48) {
/* "Multiply both sides" by -1; this also swaps the
* comparator.
*/
i = -i;
op = _Py_SwappedOp[op];
}
ตอนนี้ฟังก์ชั่นที่มีลักษณะที่ตัวแทนของลอย จำได้ว่าการลอยสามารถเขียนได้ (ละเว้นเครื่องหมาย) เป็นซิกนิแคนด์ * 2 เลขชี้กำลังและซิกนิแคนด์หมายถึงตัวเลขระหว่าง 0.5 ถึง 1:
(void) frexp(i, &exponent);
if (exponent < 0 || (size_t)exponent < nbits) {
i = 1.0;
j = 2.0;
goto Compare;
}
การตรวจสอบนี้สองสิ่ง หากเลขชี้กำลังมีค่าน้อยกว่า 0 แสดงว่า float มีค่าน้อยกว่า 1 (และมีขนาดเล็กกว่าจำนวนเต็มใด ๆ ) หรือถ้ายกกำลังน้อยกว่าจำนวนบิตในw
แล้วเรามีที่v < |w|
มาตั้งแต่ซิก * 2 ยกกำลังน้อยกว่า 2 nbits
w
ความล้มเหลวในการตรวจสอบทั้งสองลักษณะฟังก์ชั่นเพื่อดูว่าตัวแทนที่มีค่ามากกว่าจำนวนบิตใน นี่แสดงให้เห็นว่าซิกนิแคนด์และ* 2 เลขชี้กำลังมีค่ามากกว่า 2 nbitsดังนั้นv > |w|
:
if ((size_t)exponent > nbits) {
i = 2.0;
j = 1.0;
goto Compare;
}
หากการตรวจสอบนี้ไม่ประสบความสำเร็จเรารู้ว่าตัวแทนของลอยเป็นเช่นเดียวกับจำนวนบิตในจำนวนเต็มที่v
w
วิธีเดียวที่ว่าทั้งสองค่าสามารถนำมาเปรียบเทียบในขณะนี้คือการสร้างจำนวนเต็มสองจำนวนหลามใหม่จากและv
w
แนวคิดคือการละทิ้งส่วนที่เป็นเศษส่วนของv
สองส่วนที่เป็นจำนวนเต็มแล้วเพิ่มอีกหนึ่งส่วน w
เพิ่มเป็นสองเท่าและสามารถเปรียบเทียบออบเจ็กต์ Python ใหม่ทั้งสองเพื่อให้ค่าส่งคืนที่ถูกต้อง การใช้ตัวอย่างที่มีค่าน้อย4.65 < 4
จะถูกกำหนดโดยการเปรียบเทียบ(2*4)+1 == 9 < 8 == (2*4)
(return false)
{
double fracpart;
double intpart;
PyObject *result = NULL;
PyObject *one = NULL;
PyObject *vv = NULL;
PyObject *ww = w;
// snip
fracpart = modf(i, &intpart); // split i (the double that v mapped to)
vv = PyLong_FromDouble(intpart);
// snip
if (fracpart != 0.0) {
/* Shift left, and or a 1 bit into vv
* to represent the lost fraction.
*/
PyObject *temp;
one = PyLong_FromLong(1);
temp = PyNumber_Lshift(ww, one); // left-shift doubles an integer
ww = temp;
temp = PyNumber_Lshift(vv, one);
vv = temp;
temp = PyNumber_Or(vv, one); // a doubled integer is even, so this adds 1
vv = temp;
}
// snip
}
}
เพื่อความสั้นฉันได้ละทิ้งการตรวจสอบข้อผิดพลาดเพิ่มเติมและการติดตามขยะที่ต้องทำเมื่อสร้างวัตถุใหม่เหล่านี้ ไม่จำเป็นต้องพูดสิ่งนี้เพิ่มค่าใช้จ่ายเพิ่มเติมและอธิบายว่าทำไมค่าที่เน้นสีในคำถามนั้นช้ากว่าการเปรียบเทียบอย่างอื่นมาก
นี่คือบทสรุปของการตรวจสอบที่ดำเนินการโดยฟังก์ชั่นการเปรียบเทียบ
อนุญาตv
จะลอยและโยนมันทิ้งเป็น C คู่ ทีนี้ถ้าw
ยังเป็นแบบลอยตัว:
ถ้าw
เป็นจำนวนเต็ม:
สารสกัดจากสัญญาณของและv
w
หากพวกเขาแตกต่างจากนั้นเรารู้v
และw
แตกต่างและซึ่งเป็นค่าที่มากกว่า
( เครื่องหมายเหมือนกัน ) ตรวจสอบว่าw
มีบิตมากเกินไปที่จะลอย (มากกว่าsize_t
) ถ้าเป็นเช่นนั้นมีขนาดใหญ่กว่าw
v
ตรวจสอบว่าw
มี 48 หรือน้อยกว่าบิต ถ้าเป็นเช่นนั้นก็สามารถโยนได้อย่างปลอดภัยไปยัง C v
คู่โดยไม่สูญเสียความแม่นยำและเมื่อเทียบกับ
( w
มีมากกว่า 48 บิต. ตอนนี้เราจะเก็บw
เป็นจำนวนเต็มบวกที่มีการเปลี่ยนแปลงสหกรณ์เปรียบเทียบตามความเหมาะสม. )
v
พิจารณาตัวแทนของลอย หากเลขชี้กำลังเป็นลบv
จะมีค่าน้อยกว่า1
และเท่ากับจำนวนเต็มบวกใด ๆ มิฉะนั้นถ้าเลขชี้กำลังน้อยกว่าจำนวนบิตในแล้วก็ต้องน้อยกว่าw
w
หากตัวแทนของv
มีค่ามากกว่าจำนวนบิตในw
นั้นมีค่ามากกว่าv
w
( เลขชี้กำลังเป็นเช่นเดียวกับจำนวนบิตในw
. )
การตรวจสอบขั้นสุดท้าย แบ่งv
ออกเป็นส่วนจำนวนเต็มและเศษส่วน สองส่วนจำนวนเต็มและเพิ่ม 1 เพื่อชดเชยส่วนที่เป็นเศษส่วน w
ตอนนี้เป็นสองเท่าของจำนวนเต็ม เปรียบเทียบจำนวนเต็มใหม่สองตัวนี้แทนเพื่อรับผลลัพธ์