|
1 |
| -import numpy as np |
| 1 | +from decimal import Decimal, getcontext |
2 | 2 |
|
3 | 3 |
|
4 | 4 | class IEEE754:
|
5 |
| - def __init__(self, x, precision=2, |
6 |
| - force_length= None, force_exponent=None, force_mantissa=None, force_bias=None): |
7 |
| - self.precision = precision |
8 |
| - length_list = [16, 32, 64, 128, 256] |
9 |
| - exponent_list = [5, 8, 11, 15, 19] |
10 |
| - mantissa_list = [10, 23, 52, 112, 236] |
11 |
| - bias_list = [15, 127, 1023, 16383, 262143] |
12 |
| - if force_length is not None: |
13 |
| - self.length = force_length |
14 |
| - else: |
15 |
| - self.length = length_list[precision] |
16 |
| - if force_exponent is not None: |
17 |
| - self.exponent = force_exponent |
18 |
| - else: |
19 |
| - self.exponent = exponent_list[precision] |
20 |
| - if force_mantissa is not None: |
21 |
| - self.mantissa = force_mantissa |
22 |
| - else: |
23 |
| - self.mantissa = mantissa_list[precision] |
24 |
| - if force_bias is not None: |
25 |
| - self.bias = force_bias |
26 |
| - else: |
27 |
| - self.bias = bias_list[precision] |
28 |
| - self.s = 0 if x >= 0 else 1 |
29 |
| - x = abs(x) |
30 |
| - self.x = x |
31 |
| - self.i = self.integer2binary(int(x)) |
32 |
| - self.d = self.decimal2binary(x-int(x)) |
33 |
| - self.e = self.integer2binary((self.i.size-1)+self.bias) |
34 |
| - self.m = np.append(self.i[1::], self.d) |
35 |
| - self.h = '' |
36 |
| - |
37 |
| - def __str__(self): |
38 |
| - r = np.zeros(self.length, dtype=int) |
39 |
| - i_d = np.append(self.i[1::], self.d) |
40 |
| - r[0] = self.s |
41 |
| - r[1+(self.exponent - self.e.size):(self.exponent + 1):] = self.e |
42 |
| - r[(1 + self.exponent):(1 + self.exponent + i_d.size):] = i_d[0:self.mantissa] |
43 |
| - s = np.array2string(r, separator='') |
44 |
| - return s[1:-1].replace("\n", "").replace(" ", "") |
| 5 | + def __init__( |
| 6 | + self, |
| 7 | + number: str = "0.0", |
| 8 | + precision: int = 2, |
| 9 | + force_exponent: int = None, |
| 10 | + force_mantissa: int = None, |
| 11 | + ) -> None: |
| 12 | + getcontext().prec = 256 |
| 13 | + self.precision: int = precision |
| 14 | + exponent_list: list[int] = [5, 8, 11, 15, 19] |
| 15 | + mantissa_list: list[int] = [10, 23, 52, 112, 236] |
| 16 | + self.__exponent: int = ( |
| 17 | + force_exponent |
| 18 | + if force_exponent is not None |
| 19 | + else exponent_list[self.precision] |
| 20 | + ) |
| 21 | + self.__mantissa: int = ( |
| 22 | + force_mantissa |
| 23 | + if force_mantissa is not None |
| 24 | + else mantissa_list[self.precision] |
| 25 | + ) |
| 26 | + self.__bias: int = 2 ** (self.__exponent - 1) - 1 |
| 27 | + self.__edge_case: str = None |
| 28 | + self.number: Decimal = self.validate_number(number) |
| 29 | + if self.__edge_case is None: |
| 30 | + self.sign: str = self.find_sign() |
| 31 | + self.__scale, self.number = self.scale_up_to_integer(self.number, 2) |
| 32 | + self.binary: str = f"{self.number:b}" |
| 33 | + self.binary_output: str = ( |
| 34 | + f"{self.binary[:-self.__scale]}.{self.binary[-self.__scale:]}" |
| 35 | + ) |
| 36 | + self.exponent = self.find_exponent() |
| 37 | + self.mantissa = self.find_mantissa() |
| 38 | + |
| 39 | + def validate_number(self, number: str) -> Decimal: |
| 40 | + if number == "": |
| 41 | + number = "0.0" |
| 42 | + if Decimal(number).is_infinite(): |
| 43 | + if Decimal(number) > 0: |
| 44 | + # +inf: 0 11111111 00000000000000000000000 |
| 45 | + self.__edge_case = f"0 {'1' * self.__exponent} {'0' * self.__mantissa}" |
| 46 | + return Decimal("Infinity") |
| 47 | + # -inf: 1 11111111 00000000000000000000000 |
| 48 | + self.__edge_case = f"1 {'1' * self.__exponent} {'0' * self.__mantissa}" |
| 49 | + return Decimal("-Infinity") |
| 50 | + if Decimal(number).is_nan() and Decimal(number).is_snan(): |
| 51 | + # snan: 0 11111111 00000000000000000000001 |
| 52 | + self.__edge_case = ( |
| 53 | + f"0 {'1' * self.__exponent} {'0' * (self.__mantissa - 1)}1" |
| 54 | + ) |
| 55 | + return Decimal("NaN") |
| 56 | + if Decimal(number).is_nan() and Decimal(number).is_qnan(): |
| 57 | + # qnan: 0 11111111 10000000000000000000000 |
| 58 | + self.__edge_case = ( |
| 59 | + f"0 {'1' * self.__exponent} 1{'0' * (self.__mantissa - 1)}" |
| 60 | + ) |
| 61 | + return Decimal("NaN") |
| 62 | + if not number == number: |
| 63 | + # nan: 0 11111111 11111111111111111111111 |
| 64 | + self.__edge_case = f"0 {'1' * self.__exponent} {'1' * self.__mantissa}" |
| 65 | + return Decimal("NaN") |
| 66 | + if Decimal(number) == 0: |
| 67 | + if Decimal(number).is_signed(): |
| 68 | + # -0: 1 00000000 00000000000000000000000 |
| 69 | + self.__edge_case = f"1 {'0' * self.__exponent} {'0' * self.__mantissa}" |
| 70 | + else: |
| 71 | + # +0: 0 00000000 00000000000000000000000 |
| 72 | + self.__edge_case = f"0 {'0' * self.__exponent} {'0' * self.__mantissa}" |
| 73 | + return Decimal("0") |
| 74 | + if isinstance(number, int): |
| 75 | + number = f"{number}.0" |
| 76 | + if not isinstance(number, str): |
| 77 | + number = str(number) |
| 78 | + try: |
| 79 | + number = Decimal(number) |
| 80 | + except: |
| 81 | + raise ValueError(f"Invalid number: {number}") |
| 82 | + denormalized_range = self.calculate_denormalized_range( |
| 83 | + self.__exponent, self.__mantissa |
| 84 | + ) |
| 85 | + if Decimal(number) < Decimal(denormalized_range[0]): |
| 86 | + raise ValueError( |
| 87 | + f"Number is too small, must be larger than {denormalized_range[0]}, we lost both exponent and mantissa, please increase precision." |
| 88 | + ) |
| 89 | + if Decimal(number) < Decimal(denormalized_range[1]): |
| 90 | + raise ValueError( |
| 91 | + f"Number is too small, must be larger than {denormalized_range[1]}, we lost exponent, please increase precision." |
| 92 | + ) |
| 93 | + return number |
| 94 | + |
| 95 | + @staticmethod |
| 96 | + def calculate_denormalized_range(exponent_bits: int, mantissa_bits: int) -> tuple: |
| 97 | + bias = 2 ** (exponent_bits - 1) - 1 |
| 98 | + smallest_normalized = 2 ** -(bias - 1) |
| 99 | + smallest_denormalized = smallest_normalized * 2**-mantissa_bits |
| 100 | + largest_denormalized = smallest_normalized - 2 ** -(bias + mantissa_bits - 1) |
| 101 | + |
| 102 | + return (smallest_denormalized, largest_denormalized) |
| 103 | + |
| 104 | + def find_sign(self) -> str: |
| 105 | + if self.number < 0: |
| 106 | + return "1" |
| 107 | + return "0" |
45 | 108 |
|
46 | 109 | @staticmethod
|
47 |
| - def integer2binary(x): |
48 |
| - b = np.empty((0,), dtype=int) |
49 |
| - while x > 1: |
50 |
| - b = np.append(b, np.array([x % 2])) |
51 |
| - x = int(x/2) |
52 |
| - b = np.append(b, np.array([x])) |
53 |
| - b = b[::-1] |
54 |
| - return b |
55 |
| - |
56 |
| - def decimal2binary(self, x): |
57 |
| - b = np.empty((0,), dtype=int) |
58 |
| - i = 0 |
59 |
| - while x > 0 and i < self.mantissa: |
60 |
| - x = x * 2 |
61 |
| - b = np.append(b, np.array([int(x)])) |
62 |
| - x -= int(x) |
63 |
| - i += 1 |
64 |
| - return b |
65 |
| - |
66 |
| - def str2hex(self): |
67 |
| - s = str(self) |
68 |
| - for i in range(0, len(s), 4): |
69 |
| - ss = s[i:i+4] |
| 110 | + def scale_up_to_integer(number: Decimal, base: int) -> (int, int): |
| 111 | + scale = 0 |
| 112 | + while number != int(number): |
| 113 | + number *= base |
| 114 | + scale += 1 |
| 115 | + return scale, int(number) |
| 116 | + |
| 117 | + def find_exponent(self) -> str: |
| 118 | + exponent = len(self.binary) - 1 + self.__bias - self.__scale |
| 119 | + # fill with leading zeros if necessary |
| 120 | + return f"{exponent:0{self.__exponent}b}" |
| 121 | + |
| 122 | + def find_mantissa(self) -> str: |
| 123 | + # fill with trailing zeros if necessary |
| 124 | + mantissa = f"{self.binary[1:]:<0{self.__mantissa}}" |
| 125 | + if len(mantissa) > self.__mantissa: |
| 126 | + # round up if mantissa is too long |
| 127 | + mantissa = f"{mantissa[: self.__mantissa - 1]}1" |
| 128 | + return mantissa |
| 129 | + |
| 130 | + def __str__(self) -> str: |
| 131 | + if self.__edge_case is not None: |
| 132 | + return self.__edge_case |
| 133 | + return f"{self.sign} {self.exponent} {self.mantissa}" |
| 134 | + |
| 135 | + def hex(self) -> str: |
| 136 | + h = "" |
| 137 | + s = str(self).replace(" ", "") |
| 138 | + limit = len(s) - (len(s) % 4) |
| 139 | + for i in range(0, limit, 4): |
| 140 | + ss = s[i : i + 4] |
70 | 141 | si = 0
|
71 | 142 | for j in range(4):
|
72 |
| - si += int(ss[j]) * (2**(3-j)) |
| 143 | + si += int(ss[j]) * (2 ** (3 - j)) |
73 | 144 | sh = hex(si)
|
74 |
| - self.h += sh[2] |
75 |
| - return self.h |
| 145 | + h += sh[2] |
| 146 | + return h.upper() |
| 147 | + |
| 148 | + def json(self) -> dict: |
| 149 | + return { |
| 150 | + "exponent-bits": self.__exponent, |
| 151 | + "mantissa-bits": self.__mantissa, |
| 152 | + "bias": self.__bias, |
| 153 | + "sign": self.sign, |
| 154 | + "exponent": self.exponent, |
| 155 | + "mantissa": self.mantissa, |
| 156 | + "binary": self.binary, |
| 157 | + "binary_output": self.binary_output, |
| 158 | + "hex": self.hex(), |
| 159 | + "up scaled number": self.number, |
| 160 | + "scale": self.__scale, |
| 161 | + "number": self.number / (2**self.__scale), |
| 162 | + } |
| 163 | + |
| 164 | + def __repr__(self) -> str: |
| 165 | + return self.__str__() |
| 166 | + |
| 167 | + |
| 168 | +def half(x: str) -> IEEE754: |
| 169 | + return IEEE754(x, 0) |
| 170 | + |
| 171 | + |
| 172 | +def single(x: str) -> IEEE754: |
| 173 | + return IEEE754(x, 1) |
| 174 | + |
| 175 | + |
| 176 | +def double(x: str) -> IEEE754: |
| 177 | + return IEEE754(x, 2) |
| 178 | + |
| 179 | + |
| 180 | +def quadruple(x: str) -> IEEE754: |
| 181 | + return IEEE754(x, 3) |
| 182 | + |
| 183 | + |
| 184 | +def octuple(x: str) -> IEEE754: |
| 185 | + return IEEE754(x, 4) |
76 | 186 |
|
77 | 187 |
|
78 | 188 | if __name__ == "__main__":
|
79 | 189 | # with default options (Double Precision)
|
80 | 190 | x = 13.375
|
81 | 191 | a = IEEE754(x)
|
82 | 192 | # you should call the instance as a string
|
| 193 | + print(a) |
83 | 194 | print(str(a))
|
84 | 195 | print(f"{a}")
|
85 | 196 | # you can get the hexadecimal presentation like this
|
86 |
| - print(a.str2hex()) |
87 |
| - # or you can specify a precision and |
| 197 | + print(a.hex()) |
| 198 | + # or you can specify a precision |
88 | 199 | for p in range(5):
|
89 | 200 | a = IEEE754(x, p)
|
90 |
| - print("x = %f | b = %s | h = %s" % (13.375, a, a.str2hex())) |
| 201 | + print("x = %f | b = %s | h = %s" % (13.375, a, a.hex())) |
91 | 202 | # or you can use your own custom precision
|
92 |
| - a = IEEE754(x, |
93 |
| - force_length=19, |
94 |
| - force_exponent=6, |
95 |
| - force_mantissa=12, |
96 |
| - force_bias=31) |
| 203 | + a = IEEE754(x, force_exponent=6, force_mantissa=12) |
97 | 204 | print(f"{a}")
|
| 205 | + # you can get more details with json |
| 206 | + print(a.json()) |
| 207 | + |
| 208 | + # you can also call the precision functions |
| 209 | + x = 13.375 |
| 210 | + print(half(x)) |
| 211 | + print(single(x)) |
| 212 | + print(double(x)) |
| 213 | + print(quadruple(x)) |
| 214 | + print(octuple(x)) |
0 commit comments