I often require a wrapping integer type in Python, by which I actually mean a subclass of
int
where all operations are performed modulo some constant number $N$. There are two main use cases for this:
1. Working in a finite field for some cryptographic stuff, or solving problems on [Project Euler](https://projecteuler.net/about).
2. Having Python integers behave like machine registers (8, 16, 32, 64, even 128 bits - you name it.)
I decided to solve this once and for all and wrote the integer wrapper class to end all integer wrapper classes. I also managed to keep it rather compact by using *»gasp«* metaclassing.
More on that later. Using wrap
, you can create new wrapped types like this:
In [2]: class uint32(wrap, bits=32, hexrep=True): pass
In [3]: a = uint32(0xC0C4C01A)
In [4]: a
Out[4]: 0xc0c4c01a
In [5]: a ** 0x00F
Out[5]: 0x2a028000
The hexrep
parameter controls whether you want the numbers display in hex rather than the default base 10 that Python uses. I like this for my use case #2, but often not so much for the finite fields. For example, if $F$ is the field where operations happen modulo the prime number $1000000007$, use this:
In [2]: class F(wrap, mod=1_000_000_007): pass
In [3]: a = F(342_211_123)
In [4]: b = a ** -1
In [5]: a * b
Out[5]: 1
In [6]: a / b == a * a
Out[6]: True
In [7]: a // b == a * a
Out[7]: False
The division operator /
will use modular inverses while the floor division operator //
will work as usual. This is actually a bit of a gotcha, because sometimes you can perform a modular inversion modulo $2^{32}$ and the result is, of course, completely different from floor division:
In [4]: a
Out[4]: 0xc0c4c01a
In [5]: a ** 0x00F
Out[5]: 0x2a028000
In [6]: a / 13
Out[6]: 0x49e7c002
In [7]: a // 13
Out[7]: 0xed40ec6
So, if you hate that, change it. I wanted this to solve two use cases at once, so I am keeping it. Here's the code:
class wrapped(type):
def __new__(cls, name, bases, nmspc, mod=None, bits=None, hexrep=False, signed=False):
assert int in bases[0].__mro__
if None not in (mod, bits) and 1 << bits != mod:
raise ValueError('incompatible mod and bits argument.')
mod = mod or bits and 1 << bits
if mod:
for op in 'add', 'and', 'floordiv', 'lshift', 'mod', 'mul', 'or', 'rshift', 'sub', 'xor':
opname = F'__{op}__'
nmspc[F'__r{op}__'] = nmspc[opname] = lambda self, them, op=getattr(int, opname): (
self.__class__(op(self, them)))
nmspc['__rtruediv__'] = nmspc['__truediv__'] = lambda self, them, p=int.__pow__: (
self.__class__(self * p(them, -1, mod)))
nmspc['__pow__'] = lambda self, them, op=int.__pow__: self.__class__(op(self, them, mod))
nmspc['__inv__'] = lambda self, op=int.__invert__: self.__class__(op(self))
nmspc['__neg__'] = lambda self, op=int.__neg__: self.__class__(op(self))
nmspc.update(mod=mod, signed=signed)
if hexrep is True:
nib, up = divmod((mod.bit_length() - 1) - 1, 4)
nib += bool(up)
nmspc['__repr__'] = lambda self: F'{self:#0{nib}x}'
return type.__new__(cls, name, bases, nmspc)
def __call__(cls, value=0, *args, **kwargs):
if isinstance(value, int):
value = value % cls.mod
if cls.signed and (value & (cls.mod >> 1)):
value -= cls.mod
return type.__call__(cls, value)
return cls(int(value, *args, **kwargs))
class wrap(int, metaclass=wrapped):
pass
I'll explain my choices a little bit: When you create a new integer type, you have to re-implement all of the relevant [standard operators](https://docs.python.org/3/library/operator.html), even though your modification will always be conceptually the same: Perform the operation as the int
class would do it, then take the result modulo your given modulus. The metaclass wrapped
automates this. When a class of type wrapped
is defined, then all of these necessary wrappers are generated by the metaclass and adds them to the class definition. The class wrap
is a convenience class to facilitate easier creation of new classes of type wrapped
.
Fair warning: **Python 3.8** is required for the calculation of multiplicative inverses, because that's when the pow
function became able to do this out of the box.