Source code for bytemaker.bittypes.int

from __future__ import annotations

import operator
from math import ceil, log2
from typing import TYPE_CHECKING, Any

from bytemaker.bittypes.bittype import BitType, StructPackedBitType
from bytemaker.bitvector import BitsConstructible, BitVector
from bytemaker.typing_redirect import Final, Literal, Optional, TypeVar
from bytemaker.utils import is_instance_of_union

if TYPE_CHECKING:
    IntSelf = TypeVar("IntSelf", bound="Int")
else:
    try:
        from typing_redirect import Self as IntSelf
    except ImportError:
        IntSelf = TypeVar("IntSelf", bound="Int")


[docs] class Int(BitType[int]): """ A `BitType` that represents an integer. Is further subclassed into `SInt` and `UInt` for signed and unsigned integers, Class Attributes: --------------- num_bits : int The number of bits in the BitType. base_bit_type : Type[BitType] The base `BitType` this class derives from. py_type : Type[T] The Pythonic type that this Int can be converted to/from. It is int. is_signed : bool Whether the integer type is signed. Instance Attributes ------------------- bits : BitVector The underlying sequence of bits of this `Int` object. value : int The `int` value of this `Int` object. endianness : Literal["big", "little"] The endianness of this `Int` object. """ py_type = int is_signed: Final[bool] """Whether the integer type is signed.""" def __int__(self): return self.value
[docs] def to_pyint( self: BitType | BitsConstructible, signed: Optional[bool] = None, bin_format: Literal[ "twos_complement", "signed_magnitude", "ones_complement" ] = "twos_complement", ): """ Convert a bitstring to an integer. Parameters: - bitstring (str): The bitstring to convert. - signed (Optional[bool], optional) Whether the bitstring represents a signed integer (vs unsigned). Default is `cls.is_signed` or `True`. - bin_format (Optional[str], optional): The format for signed integers. Can be "twos_complement", "signed_magnitude", or "ones_complement". Default is "twos_complement". Returns: - int: The integer representation of the bitstring. """ if signed is None: bin_format_default = getattr(self, "is_signed", True) assert isinstance(bin_format_default, bool) signed = bin_format_default if isinstance(self, BitType): self = self.bits.to01() elif isinstance(self, BitVector): self = self.to01() elif is_instance_of_union(self, BitsConstructible): self = BitVector(self).to01() else: raise TypeError(f"Unsupported type: {type(self)}") bitstring: str = self if not bitstring: raise ValueError("bitstring cannot be empty") bit_length = len(bitstring) if not signed: # Unsigned integer return int(bitstring, 2) # Signed integer handling if bin_format == "twos_complement": # Handle two's complement for signed integers if bitstring[0] == "1": # Negative number int_value = -(2**bit_length) + int(bitstring, 2) else: # Positive number int_value = int(bitstring, 2) elif bin_format == "signed_magnitude" or bin_format == "sign_magnitude": # Handle sign-magnitude for signed integers if bitstring[0] == "1": # Negative number int_value = -int(bitstring[1:], 2) else: # Positive number int_value = int(bitstring[1:], 2) elif bin_format == "ones_complement": # Handle one's complement for signed integers if bitstring[0] == "1": # Negative number int_value = -((2 ** (bit_length - 1)) - int(bitstring[1:], 2) - 1) else: # Positive number int_value = int(bitstring, 2) else: raise ValueError(f"Unsupported format: {bin_format}") return int_value
[docs] @staticmethod def min_bit_length( value: int, signed: bool = True, bin_format: Optional[ Literal["twos_complement", "signed_magnitude", "ones_complement"] ] = None, ) -> int: """ Calculate the minimum number of bits required to represent an integer. Note that this is not the same as len(bin(value)), which assumes an unsigned representation (possibly with - in front). Parameters: value (int): The integer to represent. signed (bool, optional): Whether the representation format should be signed. Default is True. bin_format (Optional[str], optional): The format for signed integers. Can be "twos_complement", "signed_magnitude", or "ones_complement". Default is "twos_complement". """ n = value if not signed: if n == 0: return 1 return ceil(log2(n + 1)) else: if bin_format is None: bin_format = "twos_complement" if bin_format == "twos_complement": if n == 0: return 1 # Technically can represent 0 with 0 bits in # two's complement, but this is not useful is_greq_than_zero = n >= 0 abs_val = abs(n) is_power_of_two = (abs_val & (abs_val - 1)) == 0 if is_greq_than_zero or not is_power_of_two: return ceil(log2(abs_val + 1)) + 1 # Account for extra # at negative extreme else: return int(log2(abs_val)) + 1 elif bin_format == "signed_magnitude" or bin_format == "sign_magnitude": if n == 0: return 1 return ceil(log2(abs(n) + 1)) + 1 elif bin_format == "ones_complement": if n == 0: return 1 # Technically can represent 0 with 0 bits in # one's complement, but this is not useful return ceil(log2(abs(n) + 1)) + 1
[docs] def to_bitstring( self: Int | int, signed: bool = True, bit_length: Optional[int] = None, rep_format: Optional[ Literal["twos_complement", "signed_magnitude", "ones_complement"] ] = None, ) -> str: """ Convert an integer to a bitstring. Parameters: - integer (int): The integer to convert. - signed (bool, optional): Whether the integer should be treated as signed. Default is True. - bit_length (int, optional): The length of the bitstring. - rep_format (Optional[str], optional): The format for signed integers. Can be "twos_complement", "signed_magnitude", or "ones_complement". Default is "twos_complement". Returns: - str: The bitstring representation of the integer. """ def unsigned_int_to_bitstring(n: int, bit_length: int): if n < 0 or n >= 2**bit_length: raise ValueError("Value out of range for the specified bit_length") return bin(n)[2:].zfill(bit_length) def int_to_twos_complement(n: int, bit_length: int): """ Convert a signed integer to its two's complement binary string representation. Args: z (int): The signed integer to convert. bit_length (int): The bit length of the two's complement representation. Returns: str: The two's complement binary string representation of the integer. """ if n < -(2 ** (bit_length - 1)) or n >= 2 ** (bit_length - 1): raise ValueError( "Value out of range for the specified bit_length" " for two's-complement notation." ) if n < 0: # Calculate two's complement for negative numbers n = 2**bit_length + n return format(n, f"0{bit_length}b") def int_to_ones_complement(n: int, bit_length: int): if abs(n) > 2 ** (bit_length - 1) - 1: raise ValueError( "Value out of range for the specified bit_length" " for one's-complement notation." ) magnitude_string = unsigned_int_to_bitstring(abs(n), bit_length) if n >= 0: return magnitude_string else: return "".join( "0" if digit == "1" else "1" for digit in magnitude_string ) def int_to_signed_magnitude(n: int, bit_length: int): if abs(n) > 2 ** (bit_length - 1) - 1: raise ValueError( "Value out of range for the specified bit_length" " for sign-magnitude notation." ) if n >= 0: return "0" + unsigned_int_to_bitstring(n, bit_length - 1) else: return "1" + unsigned_int_to_bitstring(-n, bit_length - 1) if isinstance(self, Int): value = self.value elif isinstance(self, int): value = int(self) if signed and rep_format is None: rep_format = "twos_complement" if bit_length is None: bit_length = Int.min_bit_length(value, signed=signed, bin_format=rep_format) if bit_length <= 0: raise ValueError("bit_length must be a positive integer") if not signed: return unsigned_int_to_bitstring(value, bit_length) if signed: if rep_format == "twos_complement": return int_to_twos_complement(value, bit_length) elif rep_format == "signed_magnitude" or rep_format == "sign_magnitude": return int_to_signed_magnitude(value, bit_length) elif rep_format == "ones_complement": return int_to_ones_complement(value, bit_length) else: raise ValueError(f"Unsupported format: {rep_format}")
# Integer value magic methods def __add__(self: IntSelf, other: Any) -> IntSelf: return self._binary_value_op(other, operator.add) def __radd__(self: IntSelf, other: Any) -> IntSelf: return self._binary_value_op(other, lambda x, y: operator.add(y, x)) def __sub__(self: IntSelf, other: Any) -> IntSelf: return self._binary_value_op(other, operator.sub) def __rsub__(self: IntSelf, other: Any) -> IntSelf: return self._binary_value_op(other, lambda x, y: operator.sub(y, x)) def __mul__(self: IntSelf, other: Any) -> IntSelf: return self._binary_value_op(other, operator.mul) def __rmul__(self: IntSelf, other: Any) -> IntSelf: return self._binary_value_op(other, lambda x, y: operator.mul(y, x)) def __truediv__(self: IntSelf, other: Any) -> IntSelf: return self._binary_value_op(other, operator.truediv) def __rtruediv__(self: IntSelf, other: Any) -> IntSelf: return self._binary_value_op(other, lambda x, y: operator.truediv(y, x)) def __floordiv__(self: IntSelf, other: Any) -> IntSelf: return self._binary_value_op(other, operator.floordiv) def __rfloordiv__(self: IntSelf, other: Any) -> IntSelf: return self._binary_value_op(other, lambda x, y: operator.floordiv(y, x)) def __mod__(self: IntSelf, other: Any) -> IntSelf: return self._binary_value_op(other, operator.mod) def __rmod__(self: IntSelf, other: Any) -> IntSelf: return self._binary_value_op(other, lambda x, y: operator.mod(y, x)) def __pow__(self: IntSelf, other: Any) -> IntSelf: return self._binary_value_op(other, operator.pow) def __rpow__(self: IntSelf, other: Any) -> IntSelf: return self._binary_value_op(other, lambda x, y: operator.pow(y, x)) # Integer bits magic methods def __lshift__(self: IntSelf, other: Any) -> IntSelf: return self._binary_bits_op(other, operator.lshift) def __rlshift__(self: IntSelf, other: Any) -> IntSelf: return self._binary_bits_op(other, lambda x, y: operator.lshift(y, x)) def __rshift__(self: IntSelf, other: Any) -> IntSelf: return self._binary_bits_op(other, operator.rshift) def __and__(self: IntSelf, other: Any) -> IntSelf: return self._binary_bits_op(other, operator.and_)
# def __add__(self: IntSelf, other: Any) -> IntSelf: # return self._binary_value_op(other, operator.add) # def __mul__(self: IntSelf, other: Any) -> IntSelf: # return self._binary_value_op(other, operator.mul) # def __sub__(self: IntSelf, other: Any) -> IntSelf: # return self._binary_value_op(other, operator.sub) # def __truediv__(self: IntSelf, other: Any) -> IntSelf: # return self._binary_value_op(other, operator.truediv) # def __floordiv__(self: IntSelf, other: Any) -> IntSelf: # return self._binary_value_op(other, operator.floordiv) # def __mod__(self: IntSelf, other: Any) -> IntSelf: # return self._binary_value_op(other, operator.mod) # def __pow__(self: IntSelf, other: Any) -> IntSelf: # return self._binary_value_op(other, operator.pow) # def __lshift__(self: IntSelf, other: Any) -> IntSelf: # return self._binary_bits_op(other, operator.lshift) # def __rshift__(self: IntSelf, other: Any) -> IntSelf: # return self._binary_bits_op(other, operator.rshift) # def __and__(self: IntSelf, other: Any) -> IntSelf: # return self._binary_bits_op(other, operator.and_) # def __or__(self: IntSelf, other: Any) -> IntSelf: # return self._binary_bits_op(other, operator.or_) # def __xor__(self: IntSelf, other: Any) -> IntSelf: # return self._binary_bits_op(other, operator.xor)
[docs] class SignedConfig: """ A class to change the default representation and conversion for all non-user-implemented or non-user-specified signed integers simultaneously. If this is unadjusted, the default signed integer format is two's complement. """ signed_int_format: Literal[ "signed_magnitude", "ones_complement", "twos_complement" ] = "twos_complement"
[docs] class SInt(Int): """ A BitType that represents a signed integer. Use the `specialize` method to create a subclass with the desired number of bits or use one of the pre-defined subclasses. To change the signed integer format, use the `Config` class (or set the `int_format` parameter in the constructor). The default signed integer format is two's complement. Class Attributes: base_bit_type : Type[BitType] The base class (this is `SInt` for `SInt` children). num_bits : int The number of bits in the integer. is_signed : bool Whether the integer is signed (this is True for SInts). Instance Attributes: int_format : Optional[str] The format for this signed integer. Can be "twos_complement", "signed_magnitude", or "ones_complement". If this is left as `None`, the format will be taken from the `Config` class. Default is "twos_complement. value : int The `int` value of the `SInt`. bits : BitVector The bits representing the value. """ is_signed = True def __init__( self, source: Optional[(int | BitVector | BitType)] = None, value: Optional[int] = None, bits: Optional[BitVector] = None, endianness: Literal["big", "little", "source_else_big"] = "source_else_big", int_format: Optional[ Literal["twos_complement", "signed_magnitude", "ones_complement"] ] = None, ): if int_format is None: int_format = SignedConfig.signed_int_format self.int_format: Literal[ "twos_complement", "signed_magnitude", "ones_complement" ] = int_format super().__init__(source=source, value=value, bits=bits, endianness=endianness) # @classproperty # @classmethod # def int_format(cls) -> str: # return Config.signed_int_format @property def value(self): return Int.to_pyint(self.bits.to01(), signed=True, bin_format=self.int_format) @value.setter def value(self, value): str_bits = Int.to_bitstring( value, signed=True, bit_length=self.num_bits, rep_format=self.int_format ) self.bits = BitVector(str_bits)
[docs] @classmethod def specialize( cls, num_bits_: int, packing_format_letter_: Optional[str] = None, name_: Optional[str] = None, ): """ Produce a subclass of SInt with the specified number of bits. If a packing format letter is provided, the subclass will also be a StructPackedBitType and use struct's packing/unpacking functions with the provided letter. If name_ is provided, the subclass will have that name internally after class creation. Otherwise, the subclass will be named _SInt. Args: num_bits_ (int): The number of bits in integers of this type. packing_format_letter_ (Optional[str], optional): The struct packing format letter to use, if any. Defaults to None, meaning no struct (un)packing. name_ (Optional[str], optional): What to rename the subclass, if anything. Defaults to None, meaning the subclass's name will be _SInt. Returns: type[SInt]: The subclass of SInt with the specified number of bits. """ if packing_format_letter_ is not None: class _SInt(StructPackedBitType[int], cls): _num_bits = num_bits_ packing_format_letter = packing_format_letter_ @property def skip_struct_packing(self): return self.int_format != "twos_complement" else: class _SInt(cls): _num_bits = num_bits_ if name_ is not None: _SInt.__name__ = name_ return _SInt
SInt.base_bit_type = SInt
[docs] class SInt1(SInt): _num_bits = 1
[docs] class SInt2(SInt): _num_bits = 2
[docs] class SInt3(SInt): _num_bits = 3
[docs] class SInt4(SInt): _num_bits = 4
[docs] class SInt5(SInt): _num_bits = 5
[docs] class SInt6(SInt): _num_bits = 6
[docs] class SInt7(SInt): _num_bits = 7
[docs] class SInt8(StructPackedBitType, SInt): _num_bits = 8 packing_format_letter = "b" @property def skip_struct_packing(self): return SignedConfig.signed_int_format != "twos_complement"
[docs] class SInt9(SInt): _num_bits = 9
[docs] class SInt10(SInt): _num_bits = 10
[docs] class SInt11(SInt): _num_bits = 11
[docs] class SInt12(SInt): _num_bits = 12
[docs] class SInt13(SInt): _num_bits = 13
[docs] class SInt14(SInt): _num_bits = 14
[docs] class SInt15(SInt): _num_bits = 15
[docs] class SInt16(StructPackedBitType, SInt): _num_bits = 16 packing_format_letter = "h" @property def skip_struct_packing(self): return SignedConfig.signed_int_format != "twos_complement"
[docs] class SInt32(StructPackedBitType, SInt): _num_bits = 32 packing_format_letter = "i" @property def skip_struct_packing(self): return SignedConfig.signed_int_format != "twos_complement"
[docs] class SInt64(StructPackedBitType, SInt): _num_bits = 64 packing_format_letter = "q" @property def skip_struct_packing(self): return SignedConfig.signed_int_format != "twos_complement"
[docs] class SInt128(SInt): _num_bits = 128
[docs] class SInt256(SInt): _num_bits = 256
[docs] class UInt(Int): """ A BitType that represents an unsigned integer. Use the `specialize` method to create a subclass with the desired number of bits or use one of the pre-defined subclasses. Class Attributes: base_bit_type (Type[BitType]): The base class (this is UInt). num_bits (int): The number of bits in the integer. is_signed (bool): Whether the integer is signed. (This is False) Properties: value (int): The integer value of the bits. bits (BitVector): The bits representing the integer value. """ is_signed = False @property def value(self): return int(self.bits.to01(), 2) @value.setter def value(self, value): self._bits = BitVector(bin(value)[2:])
[docs] @classmethod def specialize( cls, num_bits_: int, packing_format_letter_: Optional[str] = None, name_: Optional[str] = None, ): """ Produce a subclass of UInt with the specified number of bits. If a packing format letter is provided, the subclass will also be a StructPackedBitType and use struct's packing/unpacking functions with the provided letter. If name_ is provided, the subclass will have that name internally after class creation. Otherwise, the subclass will be named _UInt. Args: num_bits_ (int): The number of bits in integers of this type. packing_format_letter_ (Optional[str], optional): The struct packing format letter to use, if any. Defaults to None, meaning no struct (un)packing. name_ (Optional[str], optional): What to rename the subclass, if anything. Defaults to None, meaning the subclass's name will be _UInt. Returns: type[UInt]: The subclass of UInt with the specified number of bits. """ if packing_format_letter_ is not None: class _UInt(StructPackedBitType[int], cls): _num_bits = num_bits_ packing_format_letter = packing_format_letter_ else: class _UInt(cls): _num_bits = num_bits_ if name_ is not None: _UInt.__name__ = name_ return _UInt
UInt.base_bit_type = UInt
[docs] class UInt1(UInt): _num_bits = 1
[docs] class UInt2(UInt): _num_bits = 2
[docs] class UInt3(UInt): _num_bits = 3
[docs] class UInt4(UInt): _num_bits = 4
[docs] class UInt5(UInt): _num_bits = 5
[docs] class UInt6(UInt): _num_bits = 6
[docs] class UInt7(UInt): _num_bits = 7
[docs] class UInt8(StructPackedBitType, UInt): _num_bits = 8 packing_format_letter = "B"
[docs] class UInt9(UInt): _num_bits = 9
[docs] class UInt10(UInt): _num_bits = 10
[docs] class UInt11(UInt): _num_bits = 11
[docs] class UInt12(UInt): _num_bits = 12
[docs] class UInt13(UInt): _num_bits = 13
[docs] class UInt14(UInt): _num_bits = 14
[docs] class UInt15(UInt): _num_bits = 15
[docs] class UInt16(StructPackedBitType, UInt): _num_bits = 16 packing_format_letter = "H"
[docs] class UInt32(StructPackedBitType, UInt): _num_bits = 32 packing_format_letter = "I"
[docs] class UInt64(StructPackedBitType, UInt): _num_bits = 64 packing_format_letter = "Q"
[docs] class UInt128(UInt): _num_bits = 128
[docs] class UInt256(UInt): _num_bits = 256
__all__ = [ "Int", "SInt", "UInt", "SignedConfig", "SInt1", "SInt2", "SInt3", "SInt4", "SInt5", "SInt6", "SInt7", "SInt8", "SInt9", "SInt10", "SInt11", "SInt12", "SInt13", "SInt14", "SInt15", "SInt16", "SInt32", "SInt64", "SInt128", "SInt256", "UInt1", "UInt2", "UInt3", "UInt4", "UInt5", "UInt6", "UInt7", "UInt8", "UInt9", "UInt10", "UInt11", "UInt12", "UInt13", "UInt14", "UInt15", "UInt16", "UInt32", "UInt64", "UInt128", "UInt256", ]