11

On my 64 bit computer the long long type has 64 bits.

print(sizeof(long long))
# prints 8

I need to use 128 bit integers and luckily GCC supports these. How can I use these within Cython?

The following doesn't work. Compiling foo.pyx containing just

cdef __int128_t x = 0

yields

$ cython foo.pyx 

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

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

foo.pyx:2:5: '__int128_t' is not a type identifier
chtenb
  • 14,924
  • 14
  • 78
  • 116

3 Answers3

11

EDIT: this is NOT a workaround anymore, this is the right way to do it. Refer also to @IanH's answer.

Now, the problem you have is that cython does not recognize your type, while gcc does. So we can try to trick cython.

File 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

File header_int128.h:

typedef __int128_t int128;

File setup.py:

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

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

Now, on my machine, when I run python setup.py build_ext --inplace, the first step passes, and the file helloworld.c is generated, and then the gcc compilation passes as well.

Now if you open the file helloworld.c, you can check that your variable foo is actually declared as an int128.

Be very careful with using this workaround. In particular, it can happen that cython will not require a cast in the C code if you assign an int128 to a int64 for example, because at that step of the process it actually does not distinguish between them.

gg349
  • 21,996
  • 5
  • 54
  • 64
  • While it compiles indeed, I can't seem to store a number of more than 64 bits in it. The code `cdef int128 bar = 1 << 64 \n print(bar)` prints `0`. On the other hand, `sizeof(int128)` says `16`, like we want. – chtenb Dec 20 '14 at 20:45
  • I did also try the simple cast in a C++ only code, and it casted an `int128` larger than an `int64` to `0`. I guess this is then not a limitation of `cython`, but something inherently wrong with `__int128_t`, at least on my machine. – gg349 Dec 21 '14 at 10:29
  • The type `__int128` works fine for me in C (other than that many functions can't handle them). Doesn't the line `ctypedef unsigned long long int128` mean that you set `int128` to be an alias for `unsigned long long`? – chtenb Dec 21 '14 at 13:55
  • Yes, but that is just a placeholder, to let the cython processo finish successfully. When you compile the code, the compiler includesse the real header and does noto read that ctypedef. – gg349 Dec 21 '14 at 16:07
  • 2
    I posted an answer containing an example of your solution. Thanks for the help! – chtenb Dec 21 '14 at 20:09
  • @gg349 could you post an example of a C code where converting from __int128 to double fails on x86_64? It seems to work fine to me... – Marc Glisse Dec 28 '14 at 18:32
  • @Marc, I have updated my compiler to gcc-4.9 and I can't reproduce the wrong casting anymore, and I updated the answer. It works with something like `__int128_t foo = 1152921504606846976;`, `foo = foo*foo;`, and then with `std::cout<<(double)foo;`. I have noticed that if I initialize `foo` to a large number (say `2**120`), I get a compilation warning, but then gcc writes `0` instead of that constant! I am not sure if I got it wrong in the first instance because of this, or because of the older version of `gcc` I was using. – gg349 Dec 29 '14 at 11:53
5

I'll throw my two cents in here.

First, the solution proposed in the other answers saying to use an external typedef is not just a workaround, that is the way the Cython docs say things like this should be done. See the relevant section. Quote: "If the header file uses typedef names such as word to refer to platform-dependent flavours of numeric types, you will need a corresponding ctypedef statement, but you don’t need to match the type exactly, just use something of the right general kind (int, float, etc). For example ctypedef int word will work okay whatever the actual size of a word is (provided the header file defines it correctly). Conversion to and from Python types, if any, will also be used for this new type."

Also, it isn't necessary to actually create a header file with a typedef for a type you've already included somewhere else along the way. Just do this

cdef extern from *:
    ctypedef int int128 "__int128_t"

Or, if you feel like keeping the name the same in Cython as it is in C,

cdef extern from *:
    ctypedef int __int128_t

Here's a test to demonstrate that this is working. If the 128 bit arithmetic is working, a > 1, and a is representable as a 64 bit integer, the first function will print the same number back again. If it is not, integer overflow should cause it to print 0. The second function shows what happens if 64 bit arithmetic is used.

Cython file

# 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

In Python, after both functions have been imported, myfunc(12321) prints the correct value while myfunc_bad(12321) prints 0.

IanH
  • 10,250
  • 1
  • 28
  • 32
3

Here is an example for using the hack proposed by @Giulio Ghirardo.

The file cbitset.px contains:

typedef unsigned __int128 bitset;

The file bitset.pyx contains:

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))

The file main.pyx contains:

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

The file setup.py contains:

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

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

Compile this using the command

python3 setup.py build_ext --inplace

and run using the command

python3 -c 'import main'
chtenb
  • 14,924
  • 14
  • 78
  • 116