При сравнении значений с плавающей точкой и целых чисел некоторым парам значений требуется гораздо больше времени для оценки, чем другим значениям схожей величины.
Например:
>>> import timeit
>>> timeit.timeit("562949953420000.7 < 562949953421000") # run 1 million times
0.5387085462592742
Но если число с плавающей точкой или целое число становится меньше или больше на определенную величину, сравнение выполняется намного быстрее:
>>> timeit.timeit("562949953420000.7 < 562949953422000") # integer increased by 1000
0.1481498428446173
>>> timeit.timeit("562949953423001.8 < 562949953421000") # float increased by 3001.1
0.1459577925548956
Изменение оператора сравнения (например, использование ==
или >
вместо) не влияет на время заметным образом.
Это не только связано с величиной, потому что выбор больших или меньших значений может привести к более быстрому сравнению, поэтому я подозреваю, что это связано с каким-то неудачным способом, которым биты выстраиваются в линию.
Очевидно, что сравнение этих значений более чем достаточно для большинства случаев использования. Мне просто любопытно, почему Python, похоже, больше борется с одними парами значений, чем с другими.
источник
Ответы:
Комментарий в исходном коде Python для объектов с плавающей точкой подтверждает, что:
Это особенно верно при сравнении числа с плавающей точкой с целым числом, потому что, в отличие от чисел с плавающей точкой, целые числа в Python могут быть произвольно большими и всегда точными. Попытка привести целое число к числу с плавающей точкой может потерять точность и сделать сравнение неточным. Попытка привести число с плавающей точкой к целому числу также не сработает, потому что любая дробная часть будет потеряна.
Чтобы обойти эту проблему, Python выполняет серию проверок, возвращая результат, если одна из проверок прошла успешно. Он сравнивает знаки двух значений, затем, является ли целое число «слишком большим», чтобы быть с плавающей точкой, затем сравнивает показатель степени с плавающей точкой с длиной целого числа. Если все эти проверки не пройдены, необходимо создать два новых объекта Python для сравнения, чтобы получить результат.
При сравнении числа с плавающей точкой
v
с целым числом / длиннымw
в худшем случае это:v
иw
имеют одинаковый знак (как положительный, так и отрицательный),w
имеет достаточно мало битов, чтобы его можно было хранить вsize_t
типе (обычно 32 или 64 бита),w
имеет не менее 49 бит,v
равен числу битов вw
.И это именно то, что мы имеем для значений в вопросе:
Мы видим, что 49 - это и показатель степени с плавающей точкой, и количество бит в целом числе. Оба числа являются положительными, и поэтому четыре критерия выше выполнены.
Выбор одного из значений, чтобы быть большим (или меньшим), может изменить число битов целого числа или значение показателя степени, и поэтому Python может определить результат сравнения без выполнения дорогостоящей финальной проверки.
Это специфично для реализации языка CPython.
Сравнение более подробно
float_richcompare
Функция обрабатывает сравнение между двумя значениямиv
иw
.Ниже приведено пошаговое описание проверок, которые выполняет функция. Комментарии в источнике Python на самом деле очень полезны при попытке понять, что делает функция, поэтому я оставил их там, где это уместно. Я также суммировал эти проверки в списке внизу ответа.
Основная идея состоит в том, чтобы сопоставить объекты Питона
v
иw
два соответствующих двойников C,i
иj
, которые затем можно легко сравнить , чтобы дать правильный результат. И Python 2, и Python 3 используют для этого одни и те же идеи (первый только обрабатываетint
иlong
печатает отдельно).Первое, что нужно сделать, это проверить, что
v
это определенно число с плавающей точкой Python, и отобразить его на C doublei
. Затем функция проверяет,w
является ли также число с плавающей запятой, и сопоставляет его с двойным Сиj
. Это лучший вариант для функции, поскольку все остальные проверки могут быть пропущены. Функция также проверяет,v
есть лиinf
илиnan
:Теперь мы знаем, что если
w
эти проверки не пройдены, это не Python. Теперь функция проверяет, является ли она целым числом Python. Если это так, самый простой тест - извлечь знакv
и знакw
(вернуть,0
если ноль,-1
если отрицательный,1
если положительный). Если знаки отличаются, это вся информация, необходимая для возврата результата сравнения:Если эта проверка не удалась, то
v
иw
есть такой же знак.Следующая проверка подсчитывает количество бит в целом числе
w
. Если в нем слишком много битов, его нельзя удерживать в виде числа с плавающей точкой, поэтому оно должно быть больше по величине, чем число с плавающей точкойv
:С другой стороны, если целое число
w
имеет 48 или меньше битов, оно может безопасно превратиться в двойное число Сиj
и сравнить:С этого момента, мы знаем, что
w
имеет 49 или более бит. Это будет удобно рассматриватьw
как положительное целое число, поэтому при необходимости измените знак и оператор сравнения:Теперь функция смотрит на показатель степени с плавающей точкой. Напомним, что число с плавающей запятой можно записать (игнорируя знак) как значение и показатель степени * 2 и что значение и представляет число от 0,5 до 1:
Это проверяет две вещи. Если показатель меньше 0, то число с плавающей точкой меньше 1 (и поэтому меньше по величине, чем любое целое число). Или, если показатель степени меньше числа битов,
w
то мы имеем это,v < |w|
поскольку значение и показатель степени * 2 меньше 2 нбит .Если эти две проверки не пройдены, функция проверяет, больше ли показатель степени, чем число бит в
w
. Это показывает , что мантисса * 2 экспонента больше 2 Nbits и такv > |w|
:Если эта проверка не удалась, мы знаем, что показатель числа с плавающей запятой
v
равен числу битов в целом числеw
.Единственный способ сравнить два значения сейчас - это построить два новых числа Python из
v
иw
. Идея состоит в том, чтобы отбросить дробную частьv
, удвоить целую часть, а затем добавить одну.w
также удваивается, и эти два новых объекта Python можно сравнить, чтобы получить правильное возвращаемое значение. Используя пример с небольшими значениями,4.65 < 4
будет определено сравнение(2*4)+1 == 9 < 8 == (2*4)
(возвращает false).Для краткости я пропустил дополнительную проверку ошибок и отслеживание мусора, которую должен выполнять Python при создании этих новых объектов. Излишне говорить, что это добавляет дополнительные издержки и объясняет, почему значения, выделенные в вопросе, сравниваются значительно медленнее, чем другие.
Вот сводка проверок, которые выполняются функцией сравнения.
Позвольте
v
быть плавающим и бросить его как C двойник. Теперь, еслиw
это тоже поплавок:Проверьте,
w
есть лиnan
илиinf
. Если это так, обрабатывайте этот особый случай отдельно в зависимости от типаw
.Если нет, то сравните
v
иw
непосредственно по их представлениям, так как C удваивается.Если
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
. Вместо этого сравните эти два новых целых числа, чтобы получить результат.источник
Используя
gmpy2
произвольные числа с плавающей точкой и целые числа, можно получить более равномерную производительность сравнения:источник
decimal
в стандартной библиотеке