如何在Cython中使用128位整数

11

在我的64位电脑上,long long类型有64位。

print(sizeof(long long))
# prints 8

我需要使用128位整数,幸运的是GCC支持这些。我该如何在Cython中使用它们?

以下方法不起作用。编译只包含以下内容的foo.pyx文件:

cdef __int128_t x = 0
产出。
$ cython foo.pyx 

Error compiling Cython file:
------------------------------------------------------------
...

cdef __int128_t x = 0
    ^
------------------------------------------------------------

foo.pyx:2:5: '__int128_t' is not a type identifier

@BrettHale 我不能只是输入 cdef __int128_t x = 0。它无法编译。 - chtenb
1
在这个上下文中,“cdef”到底是什么? - Brett Hale
2
显然,这个问题是关于Cython的,尽管标题中已经说明了。 - chtenb
1
@iharob 用同样的推理,Python 也没有64位整数,因为Python的整数是任意大小的。 - chtenb
3
这个问题不是关于Python,而是关于Cython。你可以在Cython中使用各种C结构/类型,而不需要在Python中暴露或使用它们。 - gg349
显示剩余6条评论
3个回答

11

编辑:现在这已经不再是一种解决方法,这是正确的做法。还可以参考 @IanH 的答案。

现在,你面临的问题是 cython 无法识别你的类型,而 gcc 可以。所以我们可以尝试欺骗 cython

文件 helloworld.pyx

cdef extern from "header_int128.h":
    # this is WRONG, as this would be a int64. it is here
    # just to let cython pass the first step, which is generating
    # the .c file.
    ctypedef unsigned long long int128

print "hello world"

cpdef int foo():
    cdef int128 foo = 4
    return 32

文件 header_int128.h:

typedef __int128_t int128;

文件setup.py

from distutils.core import setup
from Cython.Build import cythonize

setup(ext_modules = cythonize("helloworld.pyx"))

现在,在我的电脑上运行python setup.py build_ext --inplace时,第一步已通过,并生成文件helloworld.c,然后gcc编译也通过了。

现在如果您打开文件helloworld.c,可以检查变量foo实际上被声明为int128

非常小心地使用这个解决方法。特别是,如果您将int128赋值给int64,则在C代码中可能不需要进行转换,因为在此过程的该步骤中它实际上没有区分它们。


虽然它确实可以编译,但我似乎无法在其中存储超过64位的数字。代码cdef int128 bar = 1 << 64 \n print(bar)打印出0。另一方面,sizeof(int128)显示为16,正如我们所希望的那样。 - chtenb
在C语言中,类型__int128对我来说很好用(除了许多函数无法处理它们)。难道行ctypedef unsigned long long int128的意思不是将int128设置为unsigned long long的别名吗? - chtenb
2
我发布了一个包含你解决方案示例的答案。感谢你的帮助! - chtenb
@ gg349,你能否发布一个C代码示例,在x86_64上将__int128转换为double失败?在我看来,它似乎可以正常工作... - Marc Glisse
@Marc,我已将我的编译器更新为gcc-4.9,现在无法再复制错误的转换了,并且我已经更新了答案。它可以处理类似于__int128_t foo = 1152921504606846976;foo = foo*foo;,然后使用std::cout<<(double)foo;。我注意到,如果我将foo初始化为一个大数(比如2**120),我会得到一个编译警告,但是gcc会将该常量写为0!我不确定是因为这个还是因为我使用的旧版本的gcc导致我在第一次实例中出错。 - gg349
显示剩余2条评论

5

我来发表一下自己的想法。

首先,其他回答中提出的使用外部typedef的解决方案不仅仅是一个变通方法,这也是Cython文档中建议这样做的方式。 请参见相关部分。 引用:“如果头文件使用诸如word之类的typedef名称来引用数字类型的特定平台相关版本,则您需要相应的ctypedef语句,但您不需要完全匹配类型,只需使用正确类型的通用类型(int、float等)。例如,ctypedef int word将可以正常工作,无论word的实际大小是多少(前提是头文件正确定义了它)。如果有必要,将对该新类型执行与Python类型之间的转换。”

此外,如果您已经在其他地方包含了某种类型,那么实际上并不需要为其创建一个带有typedef的头文件。 只需这样做

cdef extern from *:
    ctypedef int int128 "__int128_t"

如果您希望在Cython中保持与C语言相同的名称,则可以这样做:
cdef extern from *:
    ctypedef int __int128_t

这里有一个测试以展示它正在工作。 如果128位算术正在工作,a > 1,并且a可以表示为64位整数,第一个函数将再次打印相同的数字。 如果不是,则整数溢出应该导致它打印0。 第二个函数展示了使用64位算术会发生什么。
Cython文件
# cython: cdivision = True

cdef extern from *:
    ctypedef int int128 "__int128_t"

def myfunc(long long a):
    cdef int128 i = a
    # set c to be the largest positive integer possible for a signed 64 bit integer
    cdef long long c = 0x7fffffffffffffff
    i *= c
    cdef long long b = i / c
    print b

def myfunc_bad(long long a):
    cdef long long i = a
    # set c to be the largest positive integer possible for a signed 64 bit integer
    cdef long long c = 0x7fffffffffffffff
    i *= c
    cdef long long b = i / c
    print b

在Python中,导入两个函数后,myfunc(12321)输出正确的值,而myfunc_bad(12321)会输出0。

3

这里有一个使用@Giulio Ghirardo提出的黑客技巧的例子。

文件cbitset.px包含:

typedef unsigned __int128 bitset;

文件bitset.pyx包含以下内容:
from libc.stdlib cimport malloc
from libc.stdio cimport printf

cdef extern from "cbitset.h":
    ctypedef unsigned long long bitset

cdef char* bitset_tostring(bitset n):
    cdef char* bitstring = <char*>malloc(8 * sizeof(bitset) * sizeof(char) + 1)
    cdef int i = 0
    while n:
        if (n & <bitset>1):
            bitstring[i] = '1'
        else:
            bitstring[i] = '0'

        n >>= <bitset>1
        i += 1
    bitstring[i] = '\0'
    return bitstring

cdef void print_bitset(bitset n):
    printf("%s\n", bitset_tostring(n))

文件main.pyx包含以下内容:

from bitset cimport print_bitset

cdef extern from "cbitset.h":
    ctypedef unsigned long long bitset

# x contains a number consisting of more than 64 1's
cdef bitset x = (<bitset>1 << 70) - 1

print_bitset(x)
# 1111111111111111111111111111111111111111111111111111111111111111111111

文件setup.py包含以下内容:
from distutils.core import setup
from Cython.Build import cythonize

setup(
    name="My app that used 128 bit ints",
    ext_modules=cythonize('main.pyx')
)

使用该命令进行编译:
python3 setup.py build_ext --inplace

并使用命令运行

python3 -c 'import main'

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