Python: 有没有一种方法可以防止自动将 int 转换为 long int?

12

考虑以下示例:

>>> from sys import maxint
>>> type(maxint)
<type 'int'>
>>> print maxint
9223372036854775807
>>> type(maxint+2)
<type 'long'>
>>> print maxint+2
9223372036854775809
>>> type((maxint+2)+maxint)
<type 'long'>
>>> print ((maxint+2)+maxint)
18446744073709551616

Python会从一个int进行autopromote,在这种情况下它是一个64位整数值(OS X,python 2.6.1),转换为Python的long整数,具有任意精度。尽管类型不同,但它们相似,Python允许使用通常的数字运算符。通常这很有用,例如能够在32位机器上使用期望64位值的代码。

然而,任意精度操作比本地int操作慢得多。例如:

>>> print maxint**maxint # execution so long it is essentially a crash

有没有一种方法可以阻止或禁止将 Python 的 int 自动转换为 Python 的 long


1
maxint**maxint是一个有>>750位小数的数字,希望你不会真的感到惊讶,这需要一些时间。另外,当一个数字无法适应32位时,应该发生什么? - Jochen Ritzel
1
你是说基本的数学运算让你的应用程序运行时间比它本来应该运行的时间长了几个小时?这听起来像是你的错误,而不是Python的错误。 - Falmarri
1
另外,如果不进行自动晋升,会发生什么?段错误(Segfault)吗?听起来你应该将数字保持在sys.maxint以下... - Falmarri
2
引发OverflowError是将其转换为long的完全合理的替代行为,尽管语言中没有这样做的机制(例如,仅对您的代码而不是库进行此操作可能没有意义,这意味着它会破坏东西)。 - Glenn Maynard
@drewk:你太过于以C语言的思维方式来考虑这个问题了。为什么数字应该被限制在32位或64位?它们并没有被保存在寄存器中。Python中有任意精度数学计算。为什么不应该一直提升精度,直到找到解决方案呢?例如Decimal模块。 - Falmarri
显示剩余9条评论
6个回答

5
如果您希望算术溢出在例如32位内溢出,您可以使用例如numpy.uint32
这将在发生溢出时给您一个警告。
>>> import numpy
>>> numpy.uint32(2**32-3) + numpy.uint32(5)
Warning: overflow encountered in ulong_scalars
2

我测试了它的速度:
>\python26\python.exe -m timeit "2**16 + 2**2"
1000000 loops, best of 3: 0.118 usec per loop

>\python26\python.exe -m timeit "2**67 + 2**65"
1000000 loops, best of 3: 0.234 usec per loop

>\python26\python.exe -m timeit -s "import numpy; numpy.seterr('ignore')" "numpy.uint32(2)**numpy.uint32(67) + numpy.uint32(2)**numpy.uint32(65)"
10000 loops, best of 3: 34.7 usec per loop

速度看起来不太乐观。

2
它会比通常的整数运算慢得多,因为NumPy每个项目处理的开销相当大。 - David Cournapeau
3
你在每个循环中调用了构造函数四次,这将非常消耗资源。相反,你应该缓存 uint32 对象本身。 - nneonneo

5

所以你想放弃“唯一正确的方法”,并在溢出方面走复古路线。真是傻。

C / C++ / C# / Java风格的溢出没有好处。它不能可靠地引发错误条件。对于C和C99,它在ANSI和POSIX中是“未定义行为”(C ++强制执行模数返回),而且这是已知的安全风险。你为什么要这样做?

Python方法可以无缝地溢出到long,这是更好的方法。我相信Perl 6正在采用相同的行为。

你可以使用Decimal模块来获取更多有限的溢出:

>>> from decimal import *
>>> from sys import maxint
>>> getcontext()
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, capitals=1,
flags=[], traps=[DivisionByZero, Overflow, InvalidOperation])

>>> d=Decimal(maxint)
>>> d
Decimal('9223372036854775807')
>>> e=Decimal(maxint)
>>> f=d**e
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/decimal.py", line 2225, in __pow__
    ans = ans._fix(context)
  File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/decimal.py", line 1589, in _fix
    return context._raise_error(Overflow, 'above Emax', self._sign)
  File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/decimal.py", line 3680, in _raise_error
    raise error(explanation)
decimal.Overflow: above Emax

您可以使用Decimal类设置精度和边界条件,溢出几乎是即时的。 您可以设置要捕获的内容,可以设置最大值和最小值。 真的 - 如何才能比这更好呢? (老实说,我不知道相对速度如何,但我怀疑它比numby快但比本机整数慢...)

对于您处理图像的特定问题,这听起来像是考虑某种形式的饱和算术的自然应用。如果您在32位算术中遇到溢出,您也可以检查明显情况下的操作数:pow,**,*。您可以考虑重载运算符并检查您不想要的条件。

如果Decimal、饱和或重载运算符无法解决问题,您可以编写扩展程序。如果您想摒弃Python的溢出方式而回归早期,则需要看天意了...


2

如果您在算法中偶尔包含 num = int(num),则可以强制将值返回为普通的 int。 如果该值很长但适合本机 int,它将降级为 int。 如果该值不适合本机 int,则仍将保持为 long


1

如果你不关心准确性,你可以将所有的数学运算模以最大整数。


评估 (maxint**maxint) % maxint 仍然会像以前一样慢。 - Glenn Maynard
2
@Glenn Maynard:pow(maxint, maxint, maxint) 更快。 - jfs

1

Int和long是历史遗留问题 - 在Python 3中,每个int都是“long”。如果您的脚本速度受到int计算的限制,那么很可能您的方法不正确。

为了给您一个恰当的答案,我们需要更多关于您正在尝试做什么的信息。


很难准确地说我在做什么,因为大约75%的工作是代码剪切和粘贴。我主要使用Perl,并在学习Python的过程中逐渐掌握了足够的Python知识,以便看到它为什么会随机变慢;这些都是32位图像签名,99.99%都是2^32。0.01%的速度非常慢,我已经追踪到是由于图像签名的32位溢出引起的。我的第一反应是(惊讶!)用C或Perl重写有问题的代码,但我想试试这个想法... - dawg
@drewk:Python使用C中的“signed int”来实现其“int”,这意味着在您的Python代码中(至少在32位平台上),任何大于或等于2 ** 31的值都会变成Python的“long”。这解释了你所看到的吗? - Craig McQueen
你是如何追踪到32位溢出的?将一个整型升级为长整型应该只需要极短的时间。 - Falmarri
@Falmarri:尝试使用$ python -m timeit '2 ** 16 + 2 ** 2'$ python -m timeit '2 ** 67 + 2 ** 65'进行比较。在我的机器上,速度差异达到了10倍。 - dawg
1
@Falmarri:“越多越好……全部更好。”我认为这不是一个二选一的问题。我只是想要一个立即发生而不是永远不会发生的故障模式。在大量溢出的情况下,两种模式都是失败的。能够设置或选择所需的行为将是很好的。Decimal 模块有它(但速度有点慢……)。作为默认值,Python 对于整数 / 长整数的行为是很好和合理的。作为唯一可能的方式——我更喜欢有选择。 - dawg
显示剩余8条评论

1

我不确定是否更快,但你可以使用numpy数组代替整数,每个数组只包含一个元素。

如果你担心的是整数幂运算,那么我们可以得出一些推论:

def smart_pow(mantissa, exponent, limit=int(math.ceil(math.log(sys.maxint)/math.log(2)))):
    if mantissa in (0, 1):
        return mantissa
    if exponent > limit:
        if mantissa == -1: 
            return -1 if exponent&1 else 1
        if mantissa > 1:
            return sys.maxint
        else: 
            return (-1-sys.maxint) if exponent&1 else sys.maxint
    else: # this *might* overflow, but at least it won't take long
        return mantissa ** exponent

1
它会非常慢 - NumPy 的速度来自于一次处理许多相同类型的项目。每个项目的开销相当大,特别是与一个 int 相比较。此外,NumPy 不一定会警告您是否溢出整数。 - David Cournapeau
1
我不知道有太多可靠或可移植的方法来获取溢出信息,即使在C语言中。如果您需要此功能,则可能需要编写代码来显式检查将会溢出的计算,例如 if (MAX_INT - b) < a,或在汇编语言中访问溢出状态标志。 - SingleNegationElimination
请参考整数安全一书,了解在C语言中实现此功能的多种方法。 - dawg
我一定是漏看了什么,因为我没有看到论文中有任何方法可以在整数溢出发生后可移植地检测它们。虽然有很多类似于我提供的示例的方法可以检测到将要发生的溢出,并且还展示了一些提供访问硬件条件寄存器以检测错误的x86汇编代码。感谢您对我的回答提供支持。 - SingleNegationElimination
@@TokenMacGuy:我想你误解了我的意思,其实我是同意那篇链接的。重新读一下我的评论,我觉得表达得不太清楚!;-) - dawg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接