ความคิดเห็นในซอร์สโค้ดของ Python สำหรับวัตถุลอยยอมรับว่า:
การเปรียบเทียบค่อนข้างเป็นฝันร้าย
นี่เป็นความจริงโดยเฉพาะอย่างยิ่งเมื่อเปรียบเทียบการลอยกับจำนวนเต็มเพราะต่างจากการลอยจำนวนเต็มใน Python สามารถใหญ่โดยพลการและแน่นอนเสมอ ความพยายามในการแปลงจำนวนเต็มไปลอยอาจทำให้ความแม่นยำลดลงและทำการเปรียบเทียบที่ไม่ถูกต้อง ความพยายามในการร่ายลอยให้เป็นจำนวนเต็มจะไม่ได้ผลเช่นกันเพราะส่วนที่เป็นเศษส่วนจะหายไป
เพื่อแก้ไขปัญหานี้ Python ดำเนินการตรวจสอบหลายชุดโดยส่งคืนผลลัพธ์หากการตรวจสอบหนึ่งประสบความสำเร็จ มันเปรียบเทียบสัญญาณของทั้งสองค่าจากนั้นไม่ว่าจะเป็นจำนวนเต็ม "ใหญ่เกินไป" ที่จะลอยแล้วเปรียบเทียบตัวแทนของลอยไปที่ความยาวของจำนวนเต็ม หากทุกอย่างของการตรวจสอบเหล่านี้ล้มเหลวก็เป็นสิ่งจำเป็นในการสร้างวัตถุสองหลามใหม่ในการเปรียบเทียบเพื่อให้ได้ผล
เมื่อเปรียบเทียบการลอยvกับจำนวนเต็ม / ยาวwกรณีที่แย่ที่สุดคือ:
vและwมีเครื่องหมายเดียวกัน (ทั้งบวกหรือลบทั้งสอง)
- จำนวนเต็ม
wมีบิตเพียงพอเล็กน้อยที่สามารถเก็บไว้ในsize_tประเภท (โดยทั่วไปคือ 32 หรือ 64 บิต)
- จำนวนเต็ม
wมีอย่างน้อย 49 บิต
- ตัวแทนของลอยเป็นเช่นเดียวกับจำนวนบิตใน
vw
และนี่คือสิ่งที่เรามีสำหรับค่าในคำถาม:
>>> 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ฟังก์ชั่นจัดการการเปรียบเทียบระหว่างสองค่าและvw
ด้านล่างนี้เป็นคำอธิบายขั้นตอนโดยขั้นตอนของการตรวจสอบที่มีประสิทธิภาพการทำงาน ความคิดเห็นในแหล่งไพ ธ อนนั้นมีประโยชน์จริง ๆ เมื่อพยายามทำความเข้าใจว่าฟังก์ชันทำอะไรดังนั้นฉันจึงทิ้งมันไว้ในที่ที่เกี่ยวข้อง ฉันได้สรุปการตรวจสอบเหล่านี้ในรายการที่ส่วนท้ายของคำตอบ
แนวคิดหลักคือการแมปวัตถุ Python vและwC สองตัวที่เหมาะสม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;
}
หากการตรวจสอบนี้ไม่ประสบความสำเร็จเรารู้ว่าตัวแทนของลอยเป็นเช่นเดียวกับจำนวนบิตในจำนวนเต็มที่vw
วิธีเดียวที่ว่าทั้งสองค่าสามารถนำมาเปรียบเทียบในขณะนี้คือการสร้างจำนวนเต็มสองจำนวนหลามใหม่จากและ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) ถ้าเป็นเช่นนั้นมีขนาดใหญ่กว่าwv
ตรวจสอบว่าwมี 48 หรือน้อยกว่าบิต ถ้าเป็นเช่นนั้นก็สามารถโยนได้อย่างปลอดภัยไปยัง C vคู่โดยไม่สูญเสียความแม่นยำและเมื่อเทียบกับ
( wมีมากกว่า 48 บิต. ตอนนี้เราจะเก็บwเป็นจำนวนเต็มบวกที่มีการเปลี่ยนแปลงสหกรณ์เปรียบเทียบตามความเหมาะสม. )
vพิจารณาตัวแทนของลอย หากเลขชี้กำลังเป็นลบvจะมีค่าน้อยกว่า1และเท่ากับจำนวนเต็มบวกใด ๆ มิฉะนั้นถ้าเลขชี้กำลังน้อยกว่าจำนวนบิตในแล้วก็ต้องน้อยกว่าww
หากตัวแทนของvมีค่ามากกว่าจำนวนบิตในwนั้นมีค่ามากกว่าvw
( เลขชี้กำลังเป็นเช่นเดียวกับจำนวนบิตในw. )
การตรวจสอบขั้นสุดท้าย แบ่งvออกเป็นส่วนจำนวนเต็มและเศษส่วน สองส่วนจำนวนเต็มและเพิ่ม 1 เพื่อชดเชยส่วนที่เป็นเศษส่วน wตอนนี้เป็นสองเท่าของจำนวนเต็ม เปรียบเทียบจำนวนเต็มใหม่สองตัวนี้แทนเพื่อรับผลลัพธ์