floatオブジェクトのPythonソースコード内のコメントは、次のことを認めています。
比較はかなり悪夢です
浮動小数点とは異なり、Pythonの整数は任意に大きく、常に正確であるため、これは浮動小数点を整数と比較する場合に特に当てはまります。整数を浮動小数点数にキャストしようとすると、精度が失われ、比較が不正確になる可能性があります。小数部が失われるため、浮動小数点を整数にキャストしようとしても機能しません。
この問題を回避するために、Pythonは一連のチェックを実行し、いずれかのチェックが成功した場合に結果を返します。2つの値の符号を比較し、整数が「大きすぎて」フロートにならないかどうかを調べ、次にフロートの指数を整数の長さと比較します。これらのチェックがすべて失敗した場合、結果を取得するには、比較する2つの新しいPythonオブジェクトを作成する必要があります。
float v
をinteger / long と比較するw
場合、最悪のケースは次のとおりです。
v
そして w
同一の符号(正または両方負の両方)を有します、
- 整数
w
は、それを保持できる十分な数のビットを持っていますsize_t
型に(通常32ビットまたは64ビット)。
- 整数
w
は少なくとも49ビットです。
- floatの指数は、
v
のビット数と同じw
です。
そして、これはまさに質問の値に対して私たちが持っているものです:
>>> import math
>>> math.frexp(562949953420000.7) # gives the float's (significand, exponent) pair
(0.9999999999976706, 49)
>>> (562949953421000).bit_length()
49
49は、浮動小数点の指数と整数のビット数の両方であることがわかります。どちらの数値も正なので、上記の4つの基準が満たされています。
いずれかの値を大きく(または小さく)選択すると、整数のビット数または指数の値が変更される可能性があるため、Pythonは高価な最終チェックを実行せずに比較の結果を判断できます。
これは、言語のCPython実装に固有です。
より詳細な比較
float_richcompare
関数は、2つの値の間の比較処理v
としますw
。
以下は、関数が実行するチェックの段階的な説明です。Pythonソースのコメントは、関数の機能を理解しようとするときに実際に非常に役立つので、関連する場所に残しておきます。これらのチェックについても、回答の下部にあるリストにまとめました。
主なアイデアは、Pythonオブジェクトv
とw
2つの適切なCのdouble、i
およびをマップするj
ことです。これらを簡単に比較して、正しい結果を得ることができます。Python 2とPython 3はどちらも同じアイデアを使用してこれを行います(前者は別々に処理int
して入力するだけlong
です)。
最初にすることは、それv
が間違いなくPython floatであることを確認し、それをC doubleにマップすることi
です。次に、関数はがw
floatでもあるかどうかを調べ、それをCのdoubleにマップします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フロートではないことがわかりました。次に、関数はそれがPython整数かどうかをチェックします。この場合、最も簡単なテストは、符号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のdouble 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];
}
これで、関数は浮動小数点の指数を調べます。floatは(signingを無視して)仮数* 2 指数として記述できること、および仮数が0.5と1の間の数を表すことを思い出してください。
(void) frexp(i, &exponent);
if (exponent < 0 || (size_t)exponent < nbits) {
i = 1.0;
j = 2.0;
goto Compare;
}
これは2つのことをチェックします。指数が0未満の場合、浮動小数点数は1より小さい(したがって、どの整数よりも大きさが小さい)。または、指数がビット数よりも小さい場合、有効桁数* 2の指数は2 nbitsより小さいため、w
それが得られます。v < |w|
これら2つのチェックに失敗すると、関数は指数がのビット数より大きいかどうかを調べw
ます。このショーその仮* 2 指数が 2以上であるNBITSのでv > |w|
:
if ((size_t)exponent > nbits) {
i = 2.0;
j = 1.0;
goto Compare;
}
このチェックが成功しなかった場合、浮動小数点の指数がv
整数のビット数と同じであることがわかりw
ます。
2つの値を比較できる唯一の方法は、v
およびから2つの新しいPython整数を作成することw
です。の考え方は、の小数部分を破棄しv
、整数部分を2倍にしてから1を加えることです。w
も2倍になり、これら2つの新しいPythonオブジェクトを比較して正しい戻り値を取得できます。小さい値の例を使用すると4.65 < 4
、比較によって決定され(2*4)+1 == 9 < 8 == (2*4)
ます(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
}
}
簡潔にするために、Pythonがこれらの新しいオブジェクトを作成するときに実行する必要がある追加のエラーチェックとガベージトラッキングを省略しました。言うまでもなく、これは追加のオーバーヘッドを追加し、質問で強調表示された値が他の値よりも比較に著しく遅い理由を説明します。
以下は、比較機能によって実行されるチェックの要約です。
させるv
フロートを可能とダブルCとしてそれをキャスト。さて、w
もがフロートである場合:
w
が整数の場合:
v
およびの兆候を抽出しw
ます。それらが異なる場合、私たちは知ってv
おりw
、異なるので、どちらがより大きな価値です。
(符号は同じです。)w
が浮動小数点数になるにはビットが多すぎるかどうかを確認します(以上size_t
)。もしそうなら、w
よりも大きさがありv
ます。
w
48ビット以下かどうかを確認します。その場合、その精度を失うことなく、Cのdoubleに安全にキャストでき、と比較できv
ます。
(w
48ビット以上あります。w
比較演算子を適切に変更した正の整数として処理します。)
floatの指数を考えv
ます。指数が負である場合には、v
より小さく、1
したがってより少ない任意の正の整数より。そうでない場合、指数がビット数よりも小さい場合は、w
それよりも小さくなければなりませんw
。
指数は、IF v
のビット数よりも大きい場合w
、次にv
よりも大きいですw
。
(指数はのビット数と同じw
です。)
最終チェック。v
整数部分と小数部分に分割します。整数部を2倍にして1を追加し、小数部を補正します。次に整数を2倍にしw
ます。代わりにこれらの2つの新しい整数を比較して結果を取得します。