比较浮点数和整数时,某些值对的求值时间要比其他类似大小的值长得多。
例如:
>>> 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在某些值对上比在其他值对上挣扎得更多。
float对象的Python源代码中的注释承认:
在比较浮点和整数时尤其如此,因为与浮点不同,Python中的整数可以任意大,并且总是精确的。尝试将整数强制转换为浮点数可能会丢失精度并使比较不准确。尝试将浮点转换为整数也行不通,因为任何小数部分都将丢失。
为了解决这个问题,Python执行一系列检查,如果其中一个检查成功,则返回结果。它比较两个值的符号,然后比较整数是否“太大”而不是浮点数,然后比较浮点数的指数与整数的长度。如果所有这些检查都失败,则需要构造两个新的Python对象进行比较,以获得结果。
当比较浮点
v
与整数/长w
时,最坏的情况是:v
和w
具有相同的符号(均为正或均为负)w
的位太少,无法保存在^{w
至少有49位v
的指数与w
中的位数相同。这正是我们对于这个问题的价值观所拥有的:
我们看到49既是浮点数的指数,也是整数的位数。这两个数字都是正数,因此符合上述四个标准。
选择一个较大(或较小)的值可以更改整数的位数或指数的值,因此Python能够确定比较的结果,而无需执行昂贵的最终检查。
这是特定于语言的CPython实现的。
更详细的比较
^{} 函数处理两个值
v
和w
之间的比较。下面是对函数执行的检查的逐步描述。Python源代码中的注释实际上对于理解函数的功能非常有帮助,所以我将它们放在相关的地方。我也在答案下面的列表中总结了这些检查。
其主要思想是将Python对象
v
和w
映射到两个适当的C双精度数i
和j
,然后可以很容易地进行比较以给出正确的结果。Python 2和Python 3都使用相同的思想(前者只分别处理int
和long
类型)。首先要做的是检查
v
绝对是Python浮点,并将其映射到C doublei
。接下来,该函数将查看w
是否也是浮点值,并将其映射到C doublej
。这是函数的最佳情况,因为可以跳过所有其他检查。函数还检查v
是inf
还是nan
:现在我们知道,如果
w
未能通过这些检查,它就不是Python浮点。现在该函数检查它是否是Python整数。如果是这样,最简单的测试是提取v
的符号和w
的符号(如果为零,则返回0
,如果为负,则返回-1
,如果为正)。如果符号不同,这是返回比较结果所需的所有信息:如果此检查失败,则
v
和w
具有相同的符号。下一个检查统计整数
w
中的位数。如果它有太多的位,那么它不可能作为一个浮点数来保存,因此它的大小必须大于浮点数v
:另一方面,如果整数
w
具有48位或更少的位,则它可以安全地在C doublej
中转动并进行比较:从这一点开始,我们知道
w
有49位或更多位。会是修道院ent将w
视为正整数,因此根据需要更改符号和比较运算符:现在该函数查看浮点的指数。回想一下,浮点数可以写为有效位*2指数(忽略符号),有效位表示0.5到1之间的数字:
这检查了两件事。如果指数小于0,则浮点值小于1(因此其大小小于任何整数)。或者,如果指数小于
w
中的位数,那么我们就得到v < |w|
,因为有效位*2指数小于2nbits。如果这两个检查失败,函数将查看指数是否大于
w
中的位数。这表明有效值*2指数大于2nbits,因此v > |w|
:如果这个检查没有成功,我们知道浮点
v
的指数与整数w
中的位数相同。现在可以比较这两个值的唯一方法是从
v
和w
构造两个新的Python整数。其思想是丢弃v
的小数部分,将整数部分加倍,然后添加一个。w
也加倍了,可以比较这两个新的Python对象以给出正确的返回值。使用值较小的示例,4.65 < 4
将通过比较(2*4)+1 == 9 < 8 == (2*4)
(返回false)来确定。为了简洁起见,我省略了Python在创建这些新对象时必须执行的额外错误检查和垃圾跟踪。不用说,这会增加额外的开销,并解释了为什么问题中突出显示的值要比其他值慢得多。
下面是比较函数执行的检查的摘要。
设
v
为浮点数并将其转换为C double。现在,如果w
也是浮点数:检查
w
是nan
还是inf
。如果是,请根据w
的类型单独处理此特殊情况。如果不是,则直接将
v
和w
表示为C doubles进行比较。如果
w
是整数:提取
v
和w
的符号。如果它们是不同的,那么我们知道v
和w
是不同的,哪个值更大。(符号相同。)检查
w
是否有太多的位作为浮点(超过size_t
)。如果是这样,w
的幅度大于v
。检查
w
是否有48位或更少的位。如果是这样,则可以安全地将其转换为C double而不会丢失其精度,并与v
进行比较。(
w
有超过48位。现在我们将把w
视为一个正整数,并根据需要更改了比较操作。)考虑浮点的指数
v
。如果指数为负,则v
小于1
,因此小于任何正整数。否则,如果指数小于w
中的位数,则它必须小于w
。如果
v
的指数大于w
中的位数,则v
大于w
。(指数与
w
中的位数相同。)最后的检查。把
v
分成整数和小数部分。将整数部分加倍,并添加1以补偿小数部分。现在将整数w
加倍。比较这两个新的整数得到结果。对任意精度的浮点和整数使用
gmpy2
可以获得更均匀的比较性能:相关问题 更多 >
编程相关推荐