I'm building float point model in python. Floating point formats are defined in class, and its operations such as multiply ... etc are defined in other modules as class. Below is directory structure:
.
├── README.md
├── chess.py
├── float_class
│ ├── __init__.py
│ ├── bitstring.py
│ ├── floatbase.py
│ ├── floatclass.py
│ ├── hw_model
│ │ ├── __init__.py
│ │ ├── fp_add
│ │ │ ├── __init__.py
│ │ │ └── fpmul.py
...
└── test.py
float_class.floatclass has Bfloat16/Float32 class which describes definition and behavior of fp itself. float_class.hw_model.fp_add.fpadd has FloatAddition class which describes behavioral model of float point addition described with classes of float_class.bitstring, which has bit manipulation class, BitString. And Bfloat16/Float32 gets these hw_model.{operations} as instances to make it as its own operation method. Here's some snippet of my codes:
floatclass.py
from abc import ABCMeta, abstractmethod, abstractclassmethod
from typing import Tuple, Optional, ClassVar, Union, TypeVar, Type, Generic, List
from typing_extensions import Self, TypeAlias
from float_class.hw_model.fp_mul.fpmul import FloatMultiplication as Mul
class FloatBase(metaclass=ABCMeta):
"""
Base class for floating-point numbers.
"""
_sign_bitpos: int = 64
_exponent_bits: int = 11
_mantissa_bits: int = 52
@property
def sign_bitpos(self) -> int:
return self._sign_bitpos
@property
def exponent_bits(self) -> int:
return self._exponent_bits
@property
def mantissa_bits(self) -> int:
return self._mantissa_bits
@property
def bias(self) -> int:
return (1 << (self._exponent_bits - 1)) - 1
@property
def exp_max(self) -> int:
return (1 << (self._exponent_bits - 1))
@property
def mant_max(self) -> int:
return (1 << self._mantissa_bits) - 1
@classmethod
def _bias(cls) -> int:
return (1 << (cls._exponent_bits - 1)) - 1
@classmethod
def _exp_max(cls) -> int:
return (1 << (cls._exponent_bits - 1))
@classmethod
def _mant_max(cls) -> int:
return (1 << cls._mantissa_bits) - 1
def __init__(self, sign: int, exponent: int, mantissa: int,
_sign_bitpos: int = 64,
_exponent_bits: int = 11,
_mantissa_bits: int = 52) -> None:
# call setter in __init__
self._sign_bitpos = _sign_bitpos
self._exponent_bits = _exponent_bits
self._mantissa_bits = _mantissa_bits
self.set(sign, exponent, mantissa)
def set(self, sign: int, exponent: int, mantissa: int) -> None:
self.sign: int = self.set_sign(sign)
self.exponent: int = self.set_exponent(exponent)
self.mantissa: int = self.set_mantissa(mantissa)
def set_sign(self, sign: int) -> int:
if not (sign == 0 or sign == 1):
raise FloatValueError(f"{self.__class__.__name__} sign value must be 0 or 1")
return sign
def set_exponent(self, exponent: int) -> int:
if not 0 - self.bias <= exponent <= self.exp_max:
raise FloatValueError(f"{self.__class__.__name__} exponent value must be in range of {-self._bias()} ~ {self.exp_max}")
return exponent
def set_mantissa(self, mantissa: int) -> int:
if not 0 <= mantissa <= self.mant_max:
raise FloatValueError(f"{self.__class__.__name__} mantissa value must be in range of 0 ~ {self.mant_max}")
return mantissa
# Operations in HW component
def __mul__(self, other: Self) -> Self:
if not isinstance(other, FloatBase):
raise FloatTypeError("Both operands should be FloatBase objects.")
if type(self) is not type(other):
raise FloatTypeError("Both operands should be of the same type.")
multiplication = Mul(self, other)
return multiplication.multiply()
...
class Bfloat16(FloatBase):
_sign_bitpos = 16
_exponent_bits = 8
_mantissa_bits = 7
def __init__(self, sign: int, exponent: int, mantissa: int) -> None:
super().__init__(sign, exponent, mantissa, self._sign_bitpos, self._exponent_bits, self._mantissa_bits)
...
class Float32(FloatBase):
_sign_bitpos = 32
_exponent_bits = 8
_mantissa_bits = 23
def __init__(self, sign: int, exponent: int, mantissa: int) -> None:
super().__init__(sign, exponent, mantissa, self._sign_bitpos, self._exponent_bits, self._mantissa_bits)
fp_mul.py
from float_class import *
from typing import Generic, TypeVar
FloatBaseT = TypeVar('FloatBaseT', bound='FloatBase')
class FloatMultiplication(Generic[FloatBaseT]):
def __init__(self, a: FloatBaseT, b: FloatBaseT) -> None:
self.a = a
self.b = b
def multiply(self):
# If input is Bfloat16, bf16_to_fp32
# Make flag of bf16 input
bf16_input = isinstance(self.a, bf16) & isinstance(self.b, bf16)
...
return
float_class._init_.py
from .floatclass import Bfloat16 as bf16
from .floatclass import Float32 as fp32
from .floatclass import FloatBase
__all__ = ['bf16', 'fp32', 'FloatBase']
test.py
a = bf16(0, 0, 0)
b = bf16(0, 1, 0)
c = a * b
Which returns: ''' Traceback (most recent call last): File "/root/workspace/git/Bfloat16/test.py", line 107, in c = a * b File "/root/workspace/git/Bfloat16/float_class/floatclass.py", line 184, in mul return multiplication.multiply() File "/root/workspace/git/Bfloat16/float_class/hw_model/fp_mul/fpmul.py", line 17, in multiply bf16_input = isinstance(self.a, bf16) & isinstance(self.b, bf16) NameError: name 'bf16' is not defined '''
I don't get it because bf16 is already defined in fpmul.py! Also, I have trouble in compilcate circular import issue with this. How can I avoid this issue? If I need to refactor or modulize my code, how can it be done? It will be very much grateful with your help.