4

I'm looking for a way to get (using Python) the maximum and minimum values of C types integers (ie uint8, int8, uint16, int16, uint32, int32, uint64, int64...) from Python.

I was expecting to find this in ctypes module

In [1]: import ctypes
In [2]: ctypes.c_uint8(256)
Out[2]: c_ubyte(0)
In [3]: ctypes.c_uint8(-1)
Out[3]: c_ubyte(255)

but I couldn't find it.

Julia have great feature for this:

julia> typemax(UInt8)
0xff

julia> typemin(UInt8)
0x00

julia> typemin(Int8)
-128

julia> typemax(Int8)
127

I'm pretty sure Python have something quite similar.

Ideally I'm even looking for a way to ensure that a given Python integer (which is said to be unbounded) can be converted safely in a C type integer of a given size. When number is not in expected interval, it should raise an exception.

Currently overflow doesn't raise exception:

In [4]: ctypes.c_uint8(256)
Out[4]: c_ubyte(0)

I saw this SO post Maximum and Minimum values for ints but it's a bit different as author is looking for min/max value of a Python integer... not a C integer (from Python)

I also noticed Detecting C types limits ("limits.h") in python? but, even if it's quite related, it doesn't really answer my question.

CristiFati
  • 38,250
  • 9
  • 50
  • 87
scls
  • 16,591
  • 10
  • 44
  • 55

1 Answers1

9

According to: [Python.Docs]: Numeric Types - int, float, complex:

Integers have unlimited precision.

Translated to code:

>>> i = 10 ** 100
>>> i
10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
>>> len(str(i))
101
>>> i.bit_length()
333

On the other hand, each C type has a fixed size (depending on platform / architecture), as clearly shown in [CPPReference]: Fundamental types.

Since [Python.Docs]: ctypes - A foreign function library for Python doesn't mention anything about types limits (note that there is some stuff not documented there), let's find them out manually.

code00.py:

#!/usr/bin/env python3

import sys
from ctypes import c_int8, c_uint8, c_byte, c_ubyte, c_int16, c_uint16, \
    c_int32, c_uint32, c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong, \
    c_int64, c_uint64, \
    sizeof


def limits(c_int_type):
    signed = c_int_type(-1).value < c_int_type(0).value
    bit_size = sizeof(c_int_type) * 8
    signed_limit = 2 ** (bit_size - 1)
    return (-signed_limit, signed_limit - 1) if signed else (0, 2 * signed_limit - 1)


def main(*argv):
    test_types = (
        c_int8,
        c_uint8,
        c_byte,
        c_ubyte,
        c_int16,
        c_uint16,
        c_int32,
        c_uint32,
        c_int,
        c_uint,
        c_long,
        c_ulong,
        c_longlong,
        c_ulonglong,
        c_int64,
        c_uint64,
    )
    for test_type in test_types:
        print("{:s} limits: ({:d}, {:d})".format(test_type.__name__, *limits(test_type)))


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.")
    sys.exit(rc)

Notes:

  • Code relies on the fact that for a certain integral type, its interval (and limits are interval's endpoints) is:

    • signed (2's complement): [-(2 bit_size - 1), 2 bit_size - 1 - 1]

    • unsigned: [0, 2 bit_size - 1]

  • To check the a type's signum, use -1 (which will automatically be converted to the upper limit (due to wrap around arithmetic) by unsigned types)

  • There are lots of duplicates the output (below), because some types are simply "aliases" to others

  • The rest of your task (creating a function that compares a Python int to the CTypes type limits, and raises an exception if it isn't) is trivial, so I didn't implement it

  • This is for demonstrating purpose only, so I didn't do any argument check

Output:

  • Win:

    • Python pc064 (064bit):

      [cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q052475749]> "e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe" ./code00.py
      Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] 064bit on win32
      
      c_byte limits: (-128, 127)
      c_ubyte limits: (0, 255)
      c_byte limits: (-128, 127)
      c_ubyte limits: (0, 255)
      c_short limits: (-32768, 32767)
      c_ushort limits: (0, 65535)
      c_long limits: (-2147483648, 2147483647)
      c_ulong limits: (0, 4294967295)
      c_long limits: (-2147483648, 2147483647)
      c_ulong limits: (0, 4294967295)
      c_long limits: (-2147483648, 2147483647)
      c_ulong limits: (0, 4294967295)
      c_longlong limits: (-9223372036854775808, 9223372036854775807)
      c_ulonglong limits: (0, 18446744073709551615)
      c_longlong limits: (-9223372036854775808, 9223372036854775807)
      c_ulonglong limits: (0, 18446744073709551615)
      
      Done.
      
    • Python pc032 (032bit):

      [cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q052475749]> "e:\Work\Dev\VEnvs\py_pc032_03.10_test0\Scripts\python.exe" ./code00.py
      Python 3.10.1 (tags/v3.10.1:2cd268a, Dec  6 2021, 18:54:59) [MSC v.1929 32 bit (Intel)] 032bit on win32
      
      c_byte limits: (-128, 127)
      c_ubyte limits: (0, 255)
      c_byte limits: (-128, 127)
      c_ubyte limits: (0, 255)
      c_short limits: (-32768, 32767)
      c_ushort limits: (0, 65535)
      c_long limits: (-2147483648, 2147483647)
      c_ulong limits: (0, 4294967295)
      c_long limits: (-2147483648, 2147483647)
      c_ulong limits: (0, 4294967295)
      c_long limits: (-2147483648, 2147483647)
      c_ulong limits: (0, 4294967295)
      c_longlong limits: (-9223372036854775808, 9223372036854775807)
      c_ulonglong limits: (0, 18446744073709551615)
      c_longlong limits: (-9223372036854775808, 9223372036854775807)
      c_ulonglong limits: (0, 18446744073709551615)
      
      Done.
      
  • Nix (Linux - WSL2) - Python pc064:

    (qaic-env) [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackOverflow/q052475749]> python ./code00.py
    Python 3.8.10 (default, Mar 15 2022, 12:22:08) [GCC 9.4.0] 064bit on linux
    
    c_byte limits: (-128, 127)
    c_ubyte limits: (0, 255)
    c_byte limits: (-128, 127)
    c_ubyte limits: (0, 255)
    c_short limits: (-32768, 32767)
    c_ushort limits: (0, 65535)
    c_int limits: (-2147483648, 2147483647)
    c_uint limits: (0, 4294967295)
    c_int limits: (-2147483648, 2147483647)
    c_uint limits: (0, 4294967295)
    c_long limits: (-9223372036854775808, 9223372036854775807)
    c_ulong limits: (0, 18446744073709551615)
    c_long limits: (-9223372036854775808, 9223372036854775807)
    c_ulong limits: (0, 18446744073709551615)
    c_long limits: (-9223372036854775808, 9223372036854775807)
    c_ulong limits: (0, 18446744073709551615)
    
    Done.
    

Notes (more or less related):

CristiFati
  • 38,250
  • 9
  • 50
  • 87
  • Great answer @CristiFati I wonder if these limits function shouldn't be add into Python ctypes source directly (or adding max/min attributes to `ctypes.c_` types. Being able to convert into C type integers raising exception can also be done quite easily using https://pastebin.com/cvm95m1x but maybe this should be part of Python core. What is your opinion? – scls Sep 25 '18 at 19:32
  • `issigned` adding a issigned attribute to `ctypes.c_ types` could also be an interesting idea – scls Sep 25 '18 at 19:35
  • You, (or I) can submit a patch to *Python* (I've done this before, some were accepted, some were rejected). Regarding exception raising, I don't think it will make it because, in *C* a value is truncated / silently converted to fit the type size (with the appropriate compiler warning, of course). Yes I suppose both things could come in handy, but most of the people that need to work at this level would also know how to do it. And the code is very short. I'm not sure how to point to them this question, and if they find smth useful they can get it. – CristiFati Sep 25 '18 at 19:42
  • But maybe it's already present (and I don't know it) or it's in the newer versions, or there is a feature request for it. Will have to check. And thank you! BYW: i think that `ctype_convert` should `return c_n`. – CristiFati Sep 25 '18 at 19:43
  • Thank you @wjandrea for the edit. However, one thing that's constant in all my answers (at least the ones from less than 5 years ago) is the *URL* format. I'm not saying it's the best one (some people might like it, while others might hate it), but its's what I consider fit (for the moment, at least). Therefore I reverted the changes. Sorry for the trouble, and no offence. – CristiFati Feb 06 '23 at 06:23
  • @CristiFati NP, and none taken. Yeah, I found the format hard to read, myself. Firstly, the module subtitles don't add anything (namely, with "int, float, complex", float and complex aren't relevant, and "A foreign function library for Python" is a description, but presumably anyone reading your answer knows what ctypes is). Secondly, mentioning the link target totally makes sense to me, but putting it first in a parenthetical hurts the flow IMHO and it'd be more readable to put it after, i.e. `Numeric Types [Python 3 docs]`, `Fundamental types [CPPReference]`, `ctypes [Python 3 docs]`. – wjandrea Feb 06 '23 at 20:08