Sebuah komentar dalam kode sumber Python untuk objek float mengakui bahwa:
Perbandingan cukup banyak mimpi buruk
Ini terutama benar ketika membandingkan float dengan integer, karena, tidak seperti float, integer dengan Python bisa berukuran besar dan selalu tepat. Mencoba melemparkan bilangan bulat ke pelampung mungkin kehilangan presisi dan membuat perbandingan tidak akurat. Mencoba melemparkan pelampung ke bilangan bulat tidak akan berhasil karena bagian pecahan akan hilang.
Untuk mengatasi masalah ini, Python melakukan serangkaian pemeriksaan, mengembalikan hasilnya jika salah satu pemeriksaan berhasil. Ini membandingkan tanda-tanda dari dua nilai, lalu apakah bilangan bulat "terlalu besar" untuk menjadi pelampung, kemudian membandingkan eksponen float dengan panjang bilangan bulat. Jika semua pemeriksaan ini gagal, perlu untuk membangun dua objek Python baru untuk membandingkan untuk mendapatkan hasilnya.
Ketika membandingkan float v
dengan integer / long w
, kasus terburuknya adalah:
v
dan w
memiliki tanda yang sama (baik positif atau negatif),
- integer
w
memiliki beberapa bit yang cukup yang dapat disimpan dalam size_t
tipe (biasanya 32 atau 64 bit),
- integer
w
memiliki setidaknya 49 bit,
- eksponen float
v
sama dengan jumlah bit dalam w
.
Dan inilah tepatnya yang kita miliki untuk nilai-nilai dalam pertanyaan:
>>> import math
>>> math.frexp(562949953420000.7) # gives the float's (significand, exponent) pair
(0.9999999999976706, 49)
>>> (562949953421000).bit_length()
49
Kita melihat bahwa 49 adalah eksponen float dan jumlah bit dalam bilangan bulat. Kedua angka positif dan keempat kriteria di atas terpenuhi.
Memilih salah satu nilai yang lebih besar (atau lebih kecil) dapat mengubah jumlah bit integer, atau nilai eksponen, sehingga Python dapat menentukan hasil perbandingan tanpa melakukan pemeriksaan akhir yang mahal.
Ini khusus untuk implementasi bahasa CPython.
Perbandingannya lebih detail
The float_richcompare
Fungsi menangani perbandingan antara dua nilai v
dan w
.
Di bawah ini adalah deskripsi langkah-demi-langkah dari pemeriksaan yang dilakukan fungsi. Komentar dalam sumber Python sebenarnya sangat membantu ketika mencoba memahami apa fungsinya, jadi saya meninggalkannya di tempat yang relevan. Saya juga meringkas cek-cek ini dalam daftar di bagian bawah jawabannya.
Ide utamanya adalah untuk memetakan objek Python v
dan w
dua C yang sesuai ganda, i
dan j
, yang kemudian dapat dengan mudah dibandingkan untuk memberikan hasil yang benar. Baik Python 2 dan Python 3 menggunakan ide yang sama untuk melakukan ini (yang sebelumnya hanya menangani int
dan long
mengetik secara terpisah).
Hal pertama yang harus dilakukan adalah memeriksa apakah v
itu benar-benar float Python dan memetakannya ke C ganda i
. Selanjutnya fungsi melihat apakah w
juga float dan memetakannya ke C ganda j
. Ini adalah skenario terbaik untuk fungsi karena semua pemeriksaan lainnya dapat dilewati. Pemeriksaan fungsi juga untuk melihat apakah v
adalah inf
atau 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;
}
Sekarang kita tahu bahwa jika w
gagal memeriksa ini, itu bukan float Python. Sekarang fungsinya memeriksa apakah itu bilangan bulat Python. Jika demikian, tes termudah adalah mengekstraksi tanda v
dan tanda w
(kembali 0
jika nol, -1
jika negatif, 1
jika positif). Jika tanda-tandanya berbeda, ini semua informasi yang diperlukan untuk mengembalikan hasil perbandingan:
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;
}
}
Jika pemeriksaan ini gagal, maka v
dan w
memiliki tanda yang sama.
Pemeriksaan selanjutnya menghitung jumlah bit dalam bilangan bulat w
. Jika bitnya terlalu banyak, maka tidak mungkin disimpan sebagai float dan karenanya harus lebih besar daripada float 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;
}
Di sisi lain, jika integer w
memiliki 48 bit atau lebih sedikit, ia dapat dengan aman diubah menjadi C ganda j
dan membandingkan:
if (nbits <= 48) {
j = PyLong_AsDouble(w);
/* It's impossible that <= 48 bits overflowed. */
assert(j != -1.0 || ! PyErr_Occurred());
goto Compare;
}
Dari titik ini dan seterusnya, kita tahu bahwa w
memiliki 49 bit atau lebih. Akan nyaman untuk diperlakukan w
sebagai bilangan bulat positif, jadi ubah tanda dan operator pembanding sesuai kebutuhan:
if (nbits <= 48) {
/* "Multiply both sides" by -1; this also swaps the
* comparator.
*/
i = -i;
op = _Py_SwappedOp[op];
}
Sekarang fungsinya terlihat pada eksponen float. Ingat bahwa pelampung dapat ditulis (tanda abaikan) sebagai signifikansi * 2 eksponen dan yang signifikan mewakili angka antara 0,5 dan 1:
(void) frexp(i, &exponent);
if (exponent < 0 || (size_t)exponent < nbits) {
i = 1.0;
j = 2.0;
goto Compare;
}
Ini memeriksa dua hal. Jika eksponen kurang dari 0 maka float lebih kecil dari 1 (dan besarnya lebih kecil dari bilangan bulat apa pun). Atau, jika eksponen kurang dari jumlah bit w
maka kita memiliki itu v < |w|
karena signifikansi * 2 eksponen kurang dari 2 nbits .
Gagal dari kedua pemeriksaan ini, fungsinya terlihat untuk melihat apakah eksponen lebih besar dari jumlah bit w
. Ini menunjukkan bahwa signifikansi * 2 eksponen lebih besar dari 2 nbits dan karenanya v > |w|
:
if ((size_t)exponent > nbits) {
i = 2.0;
j = 1.0;
goto Compare;
}
Jika pemeriksaan ini tidak berhasil, kita tahu bahwa eksponen float v
sama dengan jumlah bit dalam bilangan bulat w
.
Satu-satunya cara kedua nilai dapat dibandingkan sekarang adalah dengan membangun dua bilangan bulat Python baru dari v
dan w
. Idenya adalah untuk membuang bagian fraksional v
, menggandakan bagian integer, dan kemudian menambahkan satu. w
juga dua kali lipat dan dua objek Python baru ini dapat dibandingkan untuk memberikan nilai kembali yang benar. Menggunakan contoh dengan nilai kecil, 4.65 < 4
akan ditentukan oleh perbandingan (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
}
}
Untuk singkatnya saya telah meninggalkan tambahan pemeriksaan kesalahan dan pelacakan sampah yang harus dilakukan Python ketika membuat objek baru ini. Tidak perlu dikatakan, ini menambah overhead tambahan dan menjelaskan mengapa nilai-nilai yang disorot dalam pertanyaan jauh lebih lambat dibandingkan dibandingkan yang lain.
Berikut ini adalah ringkasan dari pemeriksaan yang dilakukan oleh fungsi perbandingan.
Membiarkan v
menjadi pelampung dan melemparkannya sebagai C ganda. Sekarang, jika w
juga pelampung:
Periksa apakah w
ini nan
atau inf
. Jika demikian, pegang kasing khusus ini secara terpisah tergantung pada jenis w
.
Jika tidak, bandingkan v
dan w
langsung dengan representasi mereka sebagai C berlipat ganda.
Jika w
bilangan bulat:
Ekstrak tanda-tanda v
dan w
. Jika mereka berbeda maka kita tahu v
dan w
berbeda dan mana yang nilainya lebih besar.
( Tanda-tandanya sama. ) Periksa apakah w
bit terlalu banyak untuk mengapung (lebih dari size_t
). Jika demikian, w
memiliki besaran lebih besar dari v
.
Periksa apakah w
memiliki 48 bit atau lebih sedikit. Jika demikian, dapat dengan aman dilemparkan ke C ganda tanpa kehilangan presisi dan dibandingkan dengan v
.
( w
memiliki lebih dari 48 bit. Sekarang kami akan memperlakukan w
sebagai bilangan bulat positif setelah mengubah op pembanding yang sesuai. )
Pertimbangkan eksponen float v
. Jika eksponen negatif, maka v
kurang dari 1
dan karena itu kurang dari bilangan bulat positif. Lain, jika eksponen kurang dari jumlah bit w
maka itu harus kurang dari w
.
Jika eksponen v
lebih besar dari jumlah bit w
maka v
lebih besar dari w
.
( Eksponen sama dengan jumlah bit dalam w
. )
Pemeriksaan terakhir. Dibagi v
menjadi bagian bilangan bulat dan pecahannya. Gandakan bagian integer dan tambahkan 1 untuk mengkompensasi bagian fraksional. Sekarang gandakan integer w
. Bandingkan kedua bilangan bulat baru ini untuk mendapatkan hasilnya.