float 객체에 대한 Python 소스 코드의 주석은 다음을 인정합니다.
비교는 거의 악몽이다
float와 정수를 비교할 때 특히 그렇습니다. float와 달리 Python의 정수는 임의로 클 수 있고 항상 정확하기 때문입니다. 정수를 부동 소수점으로 캐스트하려고하면 정밀도가 떨어지고 비교가 정확하지 않을 수 있습니다. 부동 소수점을 정수로 캐스트하려고하면 분수 부분이 손실되므로 작동하지 않습니다.
이 문제를 해결하기 위해 Python은 일련의 검사를 수행하여 검사 중 하나가 성공하면 결과를 반환합니다. 두 값의 부호를 비교 한 다음 정수가 "너무 큰"부동 소수점인지 여부를 비교 한 다음 부동 소수점 지수를 정수 길이와 비교합니다. 이러한 검사가 모두 실패하면 결과를 얻기 위해 비교할 두 개의 새 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가 float의 지수와 정수의 비트 수 둘 다임을 알 수 있습니다. 두 숫자 모두 양수이므로 위의 네 가지 기준이 충족됩니다.
더 큰 (또는 더 작은) 값 중 하나를 선택하면 정수의 비트 수 또는 지수의 값이 변경 될 수 있으므로 Python은 값 비싼 최종 검사를 수행하지 않고 비교 결과를 확인할 수 있습니다.
이는 언어의 CPython 구현에만 해당됩니다.
더 자세한 비교
이 float_richcompare
함수는 두 값 v
과 의 비교를 처리합니다 w
.
아래는 기능이 수행하는 검사에 대한 단계별 설명입니다. 파이썬 소스의 주석은 함수의 기능을 이해하려고 할 때 실제로 매우 도움이되므로 관련이있는 곳에 남겨 두었습니다. 또한 이러한 확인 사항을 답변 맨 아래 목록에 요약했습니다.
주요 아이디어는 파이썬 객체를 매핑하는 것입니다 v
및 w
이 적절한 C의 두 배에, i
그리고 j
다음 쉽게 올바른 결과를 제공하기 위해 비교 될 수있다. Python 2와 Python 3은 동일한 아이디어를 사용 하여이 작업을 수행합니다 (이전은 처리 int
및 long
유형을 별도로 처리합니다 ).
가장 먼저 할 일은 v
확실히 파이썬 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
이러한 검사에 실패하면 파이썬 플로트가 아니라는 것을 알고 있습니다. 이제 함수가 파이썬 정수인지 확인합니다. 이 경우 가장 쉬운 테스트는 부호 v
와 부호를 추출하는 것 입니다 w
( 0
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
. 비트가 너무 많으면 플로트로 보유 할 수 없으므로 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;
}
반면에 정수 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의 지수를 살펴 봅니다. float는 significand * 2 지수 로 표시 될 수 있고 significand는 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보다 작습니다 (정수보다 크기가 작음). 또는 지수가 비트 수보다 작 으면 significand * 2 지수 가 2 nbit 보다 작으므로 w
그 값을 갖습니다 .v < |w|
이 두 가지 검사에 실패하면 함수는 지수가의 비트 수보다 큰지 여부를 확인합니다 w
. 이 프로그램이 유효수 * 2 의 지수는 2보다 큰 nbits 등 v > |w|
:
if ((size_t)exponent > nbits) {
i = 2.0;
j = 1.0;
goto Compare;
}
이 검사에 실패하면 float의 지수가 v
정수의 비트 수와 같다는 것을 알 수 w
있습니다.
두 값을 비교할 수있는 유일한 방법은 v
및 에서 두 개의 새로운 Python 정수를 구성하는 것 w
입니다. 아이디어는의 소수 부분을 버리고 v
정수 부분을 두 배로 한 다음 하나를 추가하는 것입니다. w
또한 두 배가되며이 두 개의 새로운 Python 객체를 비교하여 올바른 반환 값을 얻을 수 있습니다. 작은 값을 가진 예제를 사용하면 4.65 < 4
비교에 의해 결정됩니다 (2*4)+1 == 9 < 8 == (2*4)
(거짓 반환).
{
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
float를하고 배는 C로 캐스팅. 이제 w
플로트이기도합니다.
w
정수인 경우 :
v
및 의 부호를 추출합니다 w
. 그것들이 다르다면 우리는 알고 v
있고 w
다르며 더 큰 가치입니다.
( 부호가 동일합니다. ) w
비트가 너무 많아서 부동 소수점을 초과하지 않는지 확인하십시오 size_t
. 그렇다면 w
보다 큰 크기를 갖습니다 v
.
w
비트가 48 개 이하 인지 확인하십시오 . 그렇다면 정밀도를 잃지 않고 C 배로 안전하게 캐스팅 할 수 있습니다 v
.
( w
48 비트 이상이 있습니다. w
비교 연산을 적절히 변경하여 양의 정수로 취급 합니다. )
float의 지수를 고려하십시오 v
. 지수가 음이면, v
보다 적은 1
양의 정수보다 따라서 이하. 그렇지 않으면 지수가 비트 w
수보다 적 으면보다 작아야 w
합니다.
지수의 경우 v
의 비트 수보다 큰 w
후 v
보다 크다 w
.
( 지수는의 비트 수와 동일합니다 w
. )
최종 점검. 분할 v
의 정수 및 분수 부분으로. 정수 부분을 두 배로하고 분수 부분을 보상하기 위해 1을 더하십시오. 이제 정수를 두 배로 늘리십시오 w
. 대신이 두 정수를 비교하여 결과를 얻으십시오.