# -*- coding: utf-8 -*- import sys from fixedint.compat import * from weakref import WeakValueDictionary class FixedProperty(object): def __init__(self, val, doc): self.val = val self.__doc__ = doc def __get__(self, obj, type=None): return self.val def __set__(self, obj, value): raise AttributeError("property is read-only") class FixedMetaProperty(object): def __init__(self, name, doc): self.name = name self.__doc__ = doc def __get__(self, obj, type=None): if self.name not in obj.__dict__: # this should only happen when trying to access FixedInt.prop, which help() does raise AttributeError("Attribute %s not defined on base class" % self.name) prop = obj.__dict__[self.name] return prop.__get__(obj) def __set__(self, obj, value): raise AttributeError("property %s is read-only" % self.name) if not PY3K: # While it'd be nice to put machine-sized integers into Python 2.x "int"s, # it turns out that the int methods occasionally freak out when given longs. # So, we use arbitrary-precision ints for all Pythons. int = long _class_cache = WeakValueDictionary() _doc_width = "Bit width of this integer, including the sign bit." _doc_signed = "True if this integer is a twos-complement signed type." _doc_mutable = "True if this integer is mutable (modifiable in-place)." _doc_minval = "Minimum representable value of this integer type" _doc_maxval = "Maximum representable value of this integer type" _subclass_token = object() class _FixedIntBaseMeta(type): def __new__(cls, name, bases, dict): if dict.get('_subclass_enable', None) == _subclass_token: del dict['_subclass_enable'] return type.__new__(cls, name, bases, dict) for base in bases: if issubclass(base, FixedInt): basename = base.__name__ raise Exception("Cannot subclass %s; use the %s constructor to produce new subclasses." % (basename, basename)) raise Exception("Cannot subclass this class.") def __call__(self, width, signed=True, mutable=None): signed = bool(signed) if mutable is None: # Take mutable from constructor used (FixedInt or MutableFixedInt) mutable = (self == MutableFixedInt) cachekey = (width, signed, mutable) try: return _class_cache[cachekey] except KeyError: pass if signed: min = -1<<(width-1) max = (1<<(width-1))-1 else: min = 0 max = (1<<width)-1 if mutable: bases = (MutableFixedInt,) else: bases = (FixedInt, int) dict = {} dict['width'] = FixedProperty(width, doc=_doc_width) dict['signed'] = FixedProperty(signed, doc=_doc_signed) dict['mutable'] = FixedProperty(mutable, doc=_doc_mutable) dict['minval'] = FixedProperty(min, doc=_doc_minval) dict['maxval'] = FixedProperty(max, doc=_doc_maxval) if signed: _mask1 = (1<<(width-1)) - 1 _mask2 = 1<<(width-1) def _rectify(val): return (val & _mask1) - (val & _mask2) else: _mask = (1<<width) - 1 def _rectify(val): return val & _mask dict['_rectify'] = staticmethod(_rectify) if not mutable: intbase = bases[1] def _newfunc(cls, val=0): ''' Convert an integer into a fixed-width integer. ''' return intbase.__new__(cls, _rectify(int(val))) _newfunc.__name__ = '__new__' dict['__new__'] = _newfunc name = ''.join(['Mutable'*mutable, 'U'*(not signed), 'Int', str(width)]) cls = _FixedIntMeta(name, bases, dict) _class_cache[cachekey] = cls return cls width = FixedMetaProperty('width', doc=_doc_width) signed = FixedMetaProperty('signed', doc=_doc_signed) mutable = FixedMetaProperty('mutable', doc=_doc_mutable) minval = FixedMetaProperty('minval', doc=_doc_minval) maxval = FixedMetaProperty('maxval', doc=_doc_maxval) class _FixedIntMeta(_FixedIntBaseMeta): __new__ = type.__new__ __call__ = type.__call__ def int_method(f): if isinstance(f, str): def wrapper(f2): f2.__name__ = f return int_method(f2) return wrapper else: f.__doc__ = getattr(int, f.__name__).__doc__ return f class FixedInt: __slots__ = () _subclass_enable = _subclass_token # width, signed, mutable, minval, maxval defined in metaclass # _rectify defined in metaclass # __new__ defined in metaclass if not PY3K: @int_method def __hex__(self): return '%#x' % int(self) def __oct__(self): return '%#o' % int(self) @int_method def __pow__(self, other, modulo=None): # Jython can't handle int.__pow__(x, y, None) if modulo is None: return type(self)(int.__pow__(int(self), int(other))) return type(self)(int.__pow__(int(self), int(other), modulo)) @int_method def __rpow__(self, other): return type(other)(int.__rpow__(int(self), int(other))) @int_method def __repr__(self): return '%s(%s)' % (type(self).__name__, self) @int_method def __str__(self): return str(int(self)) @classmethod def _canonicalize_index(cls, idx): if idx < 0: idx += cls.width return idx @classmethod def _canonicalize_slice(cls, slice): start = slice.start stop = slice.stop if slice.step is not None: raise ValueError("slice step unsupported") if start is None: start = 0 else: start = cls._canonicalize_index(start) if stop is None: stop = cls.width elif isinstance(stop, complex): if stop.real: raise ValueError("invalid slice stop: must be integer or pure-imaginary complex number") stop = int(stop.imag) + start else: stop = cls._canonicalize_index(stop) if 0 <= start < stop <= cls.width: return (start, stop) else: raise IndexError("invalid slice %d:%d" % (start, stop)) def __getitem__(self, item): ''' Slice the bits of an integer. x[a] gets a single bit at position a, returning a bool. x[a:b] gets a range of bits as a FixedInt. For slice notation, b may be of the form 'bj' (a complex number) to treat it as a length rather than a stop index. The result will be of the type UIntX, where X is the number of bits in the range. Examples: x[0]: equal to (x & 1) x[1:5] or x[1:4j]: equal to (x & 31) >> 1 x[:5]: equal to (x & 31) ''' if isinstance(item, slice): start, stop = self._canonicalize_slice(item) return FixedInt(stop - start, signed=False)(int(self) >> start) else: item = self._canonicalize_index(item) if 0 <= item < self.width: return bool(int(self) & (1 << item)) else: raise IndexError("index %d out of range" % item) def to_bytes(self, length=None, byteorder=sys.byteorder): if length is None: length = (self.width + 7) // 8 try: return int(self).to_bytes(length, byteorder=byteorder, signed=self.signed) except (OverflowError, AttributeError): pass val = int(self) & ((1 << (length * 8)) - 1) out = [] while length > 0: out.append(val & 0xff) val >>= 8 length -= 1 if byteorder == 'big': out = reversed(out) if PY3K: return bytes(out) else: return ''.join(map(chr, out)) @classmethod def from_bytes(cls, bytes, byteorder=sys.byteorder, signed=None): if cls in (FixedInt, MutableFixedInt): if signed is None: signed = False elif signed is None: signed = cls.signed else: raise ValueError("can't set signed with a concrete FixedInt") blen = len(bytes) try: val = int.from_bytes(bytes, byteorder=byteorder, signed=signed) except AttributeError: val = 0 if byteorder == 'big': bytes = reversed(bytes) if PY3K: for i,c in enumerate(bytes): val |= c << (8 * i) else: for i,c in enumerate(bytes): val |= ord(c) << (8 * i) if cls in (FixedInt, MutableFixedInt): return cls(blen*8, signed=signed)(val) else: return cls(val) if PY3K: @int_method def __round__(self, n=0): return int(self) # Inherited methods which are fine as-is: # complex, int, long, float, index # truediv, rtruediv, divmod, rdivmod, rlshift, rrshift # format FixedInt = add_metaclass(_FixedIntBaseMeta)(FixedInt) class MutableFixedInt(FixedInt): _subclass_enable = _subclass_token def __init__(self, val=0, base=None): ''' Convert an integer into a fixed-width integer. ''' if base is None: val = int(val) else: val = int(val, base) self._val = self._rectify(val) if PY3K: @int_method def __format__(self, format_spec): return format(self._val, format_spec) def __ipow__(self, other, modulo=None): if modulo is None: self._val = self._rectify(int.__pow__(int(self), int(other))) else: self._val = self._rectify(int.__pow__(int(self), int(other), modulo)) return self def __setitem__(self, item, value): ''' Modify a slice of an integer. x[a]=y sets a single bit at position a. x[a:b]=y sets a range of bits from an integer. See __getitem__ for more details on the slice notation. ''' value = int(value) if isinstance(item, slice): start, stop = self._canonicalize_slice(item) mask = (1 << (stop - start)) - 1 self._val = (self._val & ~(mask << start)) | ((value & mask) << start) else: item = self._canonicalize_index(item) if 0 <= item < self.width: if value: self._val |= (1 << item) else: self._val &= ~(1 << item) return bool(int(self) & (1 << item)) else: raise IndexError("index %d out of range" % item) ## Arithmetic methods def _arith_unary_factory(name, mutable): ''' Factory function producing methods for unary operations. ''' intfunc = getattr(int, name) if mutable: @int_method(name) def _f(self): return type(self)(intfunc(self._val)) else: @int_method(name) def _f(self): return type(self)(intfunc(self)) return _f _arith_unary = 'neg pos abs invert'.split() for f in _arith_unary: s = '__%s__' % f setattr(FixedInt, s, _arith_unary_factory(s, mutable=False)) setattr(MutableFixedInt, s, _arith_unary_factory(s, mutable=True)) def _arith_convert(t1, t2): if not issubclass(t2, FixedInt): return t1 # Follow C conversion rules (ISO/IEC 9899:TC3 ยง6.3.1.8) if t1.signed == t2.signed: # If both are signed or both are unsigned, return the larger type. if t1.width >= t2.width: return t1 return t2 if not t1.signed: ut, st = t1, t2 else: ut, st = t2, t1 if ut.width >= st.width: # If the unsigned type has rank >= the signed type, convert the signed type to the unsigned type. return ut else: return st def _arith_binfunc_factory(name): ''' Factory function producing methods for arithmetic operators ''' intfunc = getattr(int, name) @int_method(name) def _f(self, other): nt = _arith_convert(type(self), type(other)) return nt(intfunc(int(self), int(other))) return _f # divmod, rdivmod, truediv, rtruediv are considered non-arithmetic since they don't return ints # pow, rpow, rlshift, and rrshift are special since the LHS and RHS are very different _arith_binfunc = 'add sub mul floordiv mod lshift rshift and xor or'.split() _arith_binfunc += 'radd rsub rmul rfloordiv rmod rand rxor ror'.split() if not PY3K: _arith_binfunc += 'div rdiv'.split() for f in _arith_binfunc: s = '__%s__' % f setattr(FixedInt, s, _arith_binfunc_factory(s)) ## Non-arithmetic methods (Mutable only) def _nonarith_unary_factory_mutable(name): ''' Factory function producing methods for unary operations. ''' intfunc = getattr(int, name) @int_method(name) def _f(self): return intfunc(self._val) return _f _mutable_unary = 'int float'.split() if sys.version_info[:2] >= (2,5): _mutable_unary += ['index'] if sys.version_info[:2] >= (2,6): _mutable_unary += ['trunc'] if PY3K: _mutable_unary += 'bool'.split() else: _mutable_unary += 'nonzero long'.split() for f in _mutable_unary: s = '__%s__' % f setattr(MutableFixedInt, s, _nonarith_unary_factory_mutable(s)) def _nonarith_binfunc_factory_mutable(name): ''' Factory function producing methods for non-arithmetic binary operators on Mutable instances. ''' intfunc = getattr(int, name) @int_method(name) def _f(self, other): return intfunc(self._val, int(other)) return _f _mutable_binfunc = 'truediv rtruediv divmod rdivmod rlshift rrshift'.split() if hasattr(int, '__cmp__'): _mutable_binfunc += ['cmp'] else: _mutable_binfunc += 'lt le eq ne gt ge'.split() for f in _mutable_binfunc: s = '__%s__' % f setattr(MutableFixedInt, s, _nonarith_binfunc_factory_mutable(s)) ## In-place operators def _inplace_factory_mutable(iname, name, op): ''' Factory function producing methods for augmented assignments on Mutable instances. ''' # This uses compiled operators instead of an int.__X__ function call for speed. # Measured improvement is about 15% speed increase. exec(""" def _f(self, other): self._val = self._rectify(self._val %s int(other)) return self globals()['_f'] = _f""" % op) _f.__name__ = iname doc = list.__iadd__.__doc__ if doc: _f.__doc__ = doc.replace('__iadd__', name).replace('+=', op+'=') return _f # pow is special because it takes three arguments. _inplace_func = 'add,+ sub,- mul,* truediv,/ floordiv,// mod,% lshift,<< rshift,<< and,& or,| xor,^'.split() if not PY3K: _inplace_func += ['div,/'] for f in _inplace_func: fn, op = f.split(',') si = '__i%s__' % fn so = '__%s__' % fn setattr(MutableFixedInt, si, _inplace_factory_mutable(si, so, op))