Skip to content

Commit 6fcc9a6

Browse files
committed
Version 0.3
1 parent 973aea7 commit 6fcc9a6

File tree

4 files changed

+243
-107
lines changed

4 files changed

+243
-107
lines changed

README.md

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,12 @@ ieee754 is a Python module which finds the IEEE-754 representation of a floating
55
<li>Half Precision (16 bit: 1 bit for sign + 5 bits for exponent + 10 bits for mantissa)</li>
66
<li>Single Precision (32 bit: 1 bit for sign + 8 bits for exponent + 23 bits for mantissa)</li>
77
<li>Double Precision (64 bit: 1 bit for sign + 11 bits for exponent + 52 bits for mantissa)</li>
8-
<li>Quadrupole Precision (128 bit: 1 bit for sign + 15 bits for exponent + 112 bits for mantissa)</li>
8+
<li>Quadruple Precision (128 bit: 1 bit for sign + 15 bits for exponent + 112 bits for mantissa)</li>
99
<li>Octuple Precision (256 bit: 1 bit for sign + 19 bits for exponent + 236 bits for mantissa)</li>
1010

1111
## Prerequisites
1212

13-
ieee754 uses numpy, so you should install numpy first.
14-
```sh
15-
$ pip install numpy
16-
```
13+
ieee754 does not require any external libraries or modules.
1714

1815
## Installing
1916

@@ -33,10 +30,13 @@ from ieee754 import IEEE754
3330
x = 13.375
3431
a = IEEE754(x)
3532
# you should call the instance as a string
33+
print(a)
3634
print(str(a))
3735
print(f"{a}")
3836
# you can get the hexadecimal presentation like this
39-
print(a.str2hex())
37+
print(a.hex())
38+
# you can get more detailed information like this
39+
print(a.json())
4040
```
4141

4242
### Select a Precision
@@ -46,14 +46,27 @@ from ieee754 import IEEE754
4646

4747
for p in range(5):
4848
a = IEEE754(x, p)
49-
print("x = %f | b = %s | h = %s" % (13.375, a, a.str2hex()))
49+
print("x = %f | b = %s | h = %s" % (13.375, a, a.hex()))
50+
```
51+
52+
### Use the Precision Name as an Interface
53+
You can use the precision name as an interface to get the IEEE-754 representation of a floating point number. With this method you can get the IEEE-754 representation of a floating point number without creating an instance.
54+
```Python
55+
from ieee754 import half, single, double, quadruple, octuple
56+
57+
x = 13.375
58+
print(half(x))
59+
print(single(x))
60+
print(double(x))
61+
print(quadruple(x))
62+
print(octuple(x))
5063
```
5164

5265
### Using a Custom Precision
53-
You can force total length, exponent, and mantissa size, and also the bias.
66+
You can force exponent, and mantissa size by using force_exponent and force_mantissa parameters to create your own custom precision.
5467
```Python
55-
a = IEEE754(x, force_length=19, force_exponent=6, force_mantissa=12, force_bias=31)
56-
print(f"{a}")
68+
a = IEEE754(x, force_exponent=6, force_mantissa=12)
69+
print(a)
5770
```
5871

5972

ieee754/IEEE754.py

Lines changed: 192 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,214 @@
1-
import numpy as np
1+
from decimal import Decimal, getcontext
22

33

44
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"
45108

46109
@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]
70141
si = 0
71142
for j in range(4):
72-
si += int(ss[j]) * (2**(3-j))
143+
si += int(ss[j]) * (2 ** (3 - j))
73144
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)
76186

77187

78188
if __name__ == "__main__":
79189
# with default options (Double Precision)
80190
x = 13.375
81191
a = IEEE754(x)
82192
# you should call the instance as a string
193+
print(a)
83194
print(str(a))
84195
print(f"{a}")
85196
# 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
88199
for p in range(5):
89200
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()))
91202
# 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)
97204
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))

ieee754/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from ieee754.IEEE754 import IEEE754
1+
from ieee754.IEEE754 import IEEE754

0 commit comments

Comments
 (0)