flexfloat 0.1.1__py3-none-any.whl → 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flexfloat/__init__.py +13 -13
- flexfloat/bitarray.py +257 -257
- flexfloat/core.py +728 -728
- flexfloat/types.py +5 -5
- {flexfloat-0.1.1.dist-info → flexfloat-0.1.2.dist-info}/METADATA +147 -82
- flexfloat-0.1.2.dist-info/RECORD +10 -0
- flexfloat-0.1.2.dist-info/licenses/LICENSE +21 -0
- flexfloat-0.1.1.dist-info/RECORD +0 -9
- {flexfloat-0.1.1.dist-info → flexfloat-0.1.2.dist-info}/WHEEL +0 -0
- {flexfloat-0.1.1.dist-info → flexfloat-0.1.2.dist-info}/top_level.txt +0 -0
flexfloat/core.py
CHANGED
@@ -1,728 +1,728 @@
|
|
1
|
-
"""Core FlexFloat class implementation."""
|
2
|
-
|
3
|
-
from __future__ import annotations
|
4
|
-
|
5
|
-
import math
|
6
|
-
from typing import Final
|
7
|
-
|
8
|
-
from .bitarray import BitArray
|
9
|
-
from .types import Number
|
10
|
-
|
11
|
-
LOG10_2: Final[float] = math.log10(2)
|
12
|
-
|
13
|
-
|
14
|
-
class FlexFloat:
|
15
|
-
"""A class to represent a floating-point number with growable exponent and
|
16
|
-
fixed-size fraction. This class is designed to handle very large or very
|
17
|
-
small numbers by adjusting the exponent dynamically. While keeping the
|
18
|
-
mantissa (fraction) fixed in size.
|
19
|
-
|
20
|
-
This class follows the IEEE 754 double-precision floating-point format,
|
21
|
-
but extends it to allow for a growable exponent and a fixed-size fraction.
|
22
|
-
|
23
|
-
Attributes:
|
24
|
-
sign (bool): The sign of the number (True for negative, False for positive).
|
25
|
-
exponent (BitArray): A growable bit array representing the exponent
|
26
|
-
(uses off-set binary representation).
|
27
|
-
fraction (BitArray): A fixed-size bit array representing the fraction
|
28
|
-
(mantissa) of the number.
|
29
|
-
"""
|
30
|
-
|
31
|
-
def __init__(
|
32
|
-
self,
|
33
|
-
sign: bool = False,
|
34
|
-
exponent: BitArray | None = None,
|
35
|
-
fraction: BitArray | None = None,
|
36
|
-
):
|
37
|
-
"""Initialize a FlexFloat instance.
|
38
|
-
|
39
|
-
Args:
|
40
|
-
sign (bool): The sign of the number (True for negative, False for positive).
|
41
|
-
exponent (BitArray | None): The exponent bit array (If None, represents 0).
|
42
|
-
fraction (BitArray | None): The fraction bit array (If None, represents 0).
|
43
|
-
"""
|
44
|
-
self.sign = sign
|
45
|
-
self.exponent = exponent if exponent is not None else BitArray.zeros(11)
|
46
|
-
self.fraction = fraction if fraction is not None else BitArray.zeros(52)
|
47
|
-
|
48
|
-
@classmethod
|
49
|
-
def from_float(cls, value: Number) -> FlexFloat:
|
50
|
-
"""Create a FlexFloat instance from a number.
|
51
|
-
|
52
|
-
Args:
|
53
|
-
value (Number): The number to convert to FlexFloat.
|
54
|
-
Returns:
|
55
|
-
FlexFloat: A new FlexFloat instance representing the number.
|
56
|
-
"""
|
57
|
-
value = float(value)
|
58
|
-
bits = BitArray.from_float(value)
|
59
|
-
|
60
|
-
return cls(sign=bits[0], exponent=bits[1:12], fraction=bits[12:64])
|
61
|
-
|
62
|
-
def to_float(self) -> float:
|
63
|
-
"""Convert the FlexFloat instance back to a 64-bit float.
|
64
|
-
|
65
|
-
If float is bigger than 64 bits, it will truncate the value to fit.
|
66
|
-
|
67
|
-
Returns:
|
68
|
-
float: The floating-point number represented by the FlexFloat instance.
|
69
|
-
Raises:
|
70
|
-
ValueError: If the exponent or fraction lengths are not as expected.
|
71
|
-
"""
|
72
|
-
if len(self.exponent) < 11 or len(self.fraction) < 52:
|
73
|
-
raise ValueError("Must be a standard 64-bit FlexFloat")
|
74
|
-
|
75
|
-
bits = BitArray([self.sign]) + self.exponent[:11] + self.fraction[:52]
|
76
|
-
return bits.to_float()
|
77
|
-
|
78
|
-
def __repr__(self) -> str:
|
79
|
-
"""Return a string representation of the FlexFloat instance.
|
80
|
-
|
81
|
-
Returns:
|
82
|
-
str: A string representation of the FlexFloat instance.
|
83
|
-
"""
|
84
|
-
return (
|
85
|
-
"FlexFloat("
|
86
|
-
f"sign={self.sign}, "
|
87
|
-
f"exponent={self.exponent}, "
|
88
|
-
f"fraction={self.fraction})"
|
89
|
-
)
|
90
|
-
|
91
|
-
def pretty(self) -> str:
|
92
|
-
"""Return an easier to read string representation of the FlexFloat instance.
|
93
|
-
Mainly converts the exponent and fraction to integers for readability.
|
94
|
-
|
95
|
-
Returns:
|
96
|
-
str: A pretty string representation of the FlexFloat instance.
|
97
|
-
"""
|
98
|
-
sign = "-" if self.sign else ""
|
99
|
-
exponent_value = self.exponent.to_signed_int() + 1
|
100
|
-
fraction_value = self.fraction.to_int()
|
101
|
-
return f"{sign}FlexFloat(exponent={exponent_value}, fraction={fraction_value})"
|
102
|
-
|
103
|
-
@classmethod
|
104
|
-
def nan(cls) -> FlexFloat:
|
105
|
-
"""Create a FlexFloat instance representing NaN (Not a Number).
|
106
|
-
|
107
|
-
Returns:
|
108
|
-
FlexFloat: A new FlexFloat instance representing NaN.
|
109
|
-
"""
|
110
|
-
exponent = BitArray.ones(11)
|
111
|
-
fraction = BitArray.ones(52)
|
112
|
-
return cls(sign=True, exponent=exponent, fraction=fraction)
|
113
|
-
|
114
|
-
@classmethod
|
115
|
-
def infinity(cls, sign: bool = False) -> FlexFloat:
|
116
|
-
"""Create a FlexFloat instance representing Infinity.
|
117
|
-
|
118
|
-
Args:
|
119
|
-
sign (bool): Indicates if the infinity is negative.
|
120
|
-
Returns:
|
121
|
-
FlexFloat: A new FlexFloat instance representing Infinity.
|
122
|
-
"""
|
123
|
-
exponent = BitArray.ones(11)
|
124
|
-
fraction = BitArray.zeros(52)
|
125
|
-
return cls(sign=sign, exponent=exponent, fraction=fraction)
|
126
|
-
|
127
|
-
@classmethod
|
128
|
-
def zero(cls) -> FlexFloat:
|
129
|
-
"""Create a FlexFloat instance representing zero.
|
130
|
-
|
131
|
-
Returns:
|
132
|
-
FlexFloat: A new FlexFloat instance representing zero.
|
133
|
-
"""
|
134
|
-
exponent = BitArray.zeros(11)
|
135
|
-
fraction = BitArray.zeros(52)
|
136
|
-
return cls(sign=False, exponent=exponent, fraction=fraction)
|
137
|
-
|
138
|
-
def _is_special_exponent(self) -> bool:
|
139
|
-
"""Check if the exponent represents a special value (NaN or Infinity).
|
140
|
-
|
141
|
-
Returns:
|
142
|
-
bool: True if the exponent is at its maximum value, False otherwise.
|
143
|
-
"""
|
144
|
-
# In IEEE 754, special values have all exponent bits set to 1
|
145
|
-
# This corresponds to the maximum value in the unsigned representation
|
146
|
-
# For signed offset binary, the maximum value is 2^(n-1) - 1
|
147
|
-
# where n is the number of bits
|
148
|
-
max_signed_value = (1 << (len(self.exponent) - 1)) - 1
|
149
|
-
return self.exponent.to_signed_int() == max_signed_value
|
150
|
-
|
151
|
-
def is_nan(self) -> bool:
|
152
|
-
"""Check if the FlexFloat instance represents NaN (Not a Number).
|
153
|
-
|
154
|
-
Returns:
|
155
|
-
bool: True if the FlexFloat instance is NaN, False otherwise.
|
156
|
-
"""
|
157
|
-
return self._is_special_exponent() and any(self.fraction)
|
158
|
-
|
159
|
-
def is_infinity(self) -> bool:
|
160
|
-
"""Check if the FlexFloat instance represents Infinity.
|
161
|
-
|
162
|
-
Returns:
|
163
|
-
bool: True if the FlexFloat instance is Infinity, False otherwise.
|
164
|
-
"""
|
165
|
-
return self._is_special_exponent() and not any(self.fraction)
|
166
|
-
|
167
|
-
def is_zero(self) -> bool:
|
168
|
-
"""Check if the FlexFloat instance represents zero.
|
169
|
-
|
170
|
-
Returns:
|
171
|
-
bool: True if the FlexFloat instance is zero, False otherwise.
|
172
|
-
"""
|
173
|
-
return not any(self.exponent) and not any(self.fraction)
|
174
|
-
|
175
|
-
def copy(self) -> FlexFloat:
|
176
|
-
"""Create a copy of the FlexFloat instance.
|
177
|
-
|
178
|
-
Returns:
|
179
|
-
FlexFloat: A new FlexFloat instance with the same data as the original.
|
180
|
-
"""
|
181
|
-
return FlexFloat(
|
182
|
-
sign=self.sign, exponent=self.exponent.copy(), fraction=self.fraction.copy()
|
183
|
-
)
|
184
|
-
|
185
|
-
def __str__(self) -> str:
|
186
|
-
"""Float representation of the FlexFloat using a generic algorithm.
|
187
|
-
|
188
|
-
This implementation doesn't rely on Python's float conversion and instead
|
189
|
-
implements the formatting logic directly, making it work for any exponent size.
|
190
|
-
|
191
|
-
Currently, it only operates in scientific notation with 5 decimal places.
|
192
|
-
"""
|
193
|
-
sign_str = "-" if self.sign else ""
|
194
|
-
# Handle special cases first
|
195
|
-
if self.is_nan():
|
196
|
-
return "nan"
|
197
|
-
|
198
|
-
if self.is_infinity():
|
199
|
-
return f"{sign_str}inf"
|
200
|
-
|
201
|
-
if self.is_zero():
|
202
|
-
return f"{sign_str}0.00000e+00"
|
203
|
-
|
204
|
-
exponent = self.exponent.to_signed_int() + 1
|
205
|
-
|
206
|
-
# Convert fraction to decimal value between 1 and 2
|
207
|
-
# (starting with 1.0 for the implicit leading bit)
|
208
|
-
mantissa = 1.0
|
209
|
-
for i, bit in enumerate(self.fraction):
|
210
|
-
if bit:
|
211
|
-
mantissa += 1.0 / (1 << (i + 1))
|
212
|
-
|
213
|
-
# To avoid overflow with very large exponents, work in log space
|
214
|
-
# log10(mantissa * 2^exponent) = log10(mantissa) + exponent * log10(2)
|
215
|
-
log10_mantissa = math.log10(mantissa)
|
216
|
-
log10_total = log10_mantissa + exponent * LOG10_2
|
217
|
-
|
218
|
-
decimal_exponent = int(log10_total)
|
219
|
-
|
220
|
-
log10_normalized = log10_total - decimal_exponent
|
221
|
-
normalized_mantissa = math.pow(10, log10_normalized)
|
222
|
-
|
223
|
-
# Ensure the mantissa is properly normalized (between 1.0 and 10.0)
|
224
|
-
while normalized_mantissa >= 10.0:
|
225
|
-
normalized_mantissa /= 10.0
|
226
|
-
decimal_exponent += 1
|
227
|
-
while normalized_mantissa < 1.0:
|
228
|
-
normalized_mantissa *= 10.0
|
229
|
-
decimal_exponent -= 1
|
230
|
-
|
231
|
-
# Format with 5 decimal places
|
232
|
-
return f"{sign_str}{normalized_mantissa:.5f}e{decimal_exponent:+03d}"
|
233
|
-
|
234
|
-
def __neg__(self) -> FlexFloat:
|
235
|
-
"""Negate the FlexFloat instance."""
|
236
|
-
return FlexFloat(
|
237
|
-
sign=not self.sign,
|
238
|
-
exponent=self.exponent.copy(),
|
239
|
-
fraction=self.fraction.copy(),
|
240
|
-
)
|
241
|
-
|
242
|
-
@staticmethod
|
243
|
-
def _grow_exponent(exponent: int, exponent_length: int) -> int:
|
244
|
-
"""Grow the exponent if it exceeds the maximum value for the current length.
|
245
|
-
|
246
|
-
Args:
|
247
|
-
exponent (int): The current exponent value.
|
248
|
-
exponent_length (int): The current length of the exponent in bits.
|
249
|
-
Returns:
|
250
|
-
int: The new exponent length if it needs to be grown, otherwise the same
|
251
|
-
length.
|
252
|
-
"""
|
253
|
-
while True:
|
254
|
-
half = 1 << (exponent_length - 1)
|
255
|
-
min_exponent = -half
|
256
|
-
max_exponent = half - 1
|
257
|
-
|
258
|
-
if min_exponent <= exponent <= max_exponent:
|
259
|
-
break
|
260
|
-
exponent_length += 1
|
261
|
-
|
262
|
-
return exponent_length
|
263
|
-
|
264
|
-
def __add__(self, other: FlexFloat | Number) -> FlexFloat:
|
265
|
-
"""Add two FlexFloat instances together.
|
266
|
-
|
267
|
-
Args:
|
268
|
-
other (FlexFloat | float | int): The other FlexFloat instance to add.
|
269
|
-
Returns:
|
270
|
-
FlexFloat: A new FlexFloat instance representing the sum.
|
271
|
-
"""
|
272
|
-
if isinstance(other, Number):
|
273
|
-
other = FlexFloat.from_float(other)
|
274
|
-
if not isinstance(other, FlexFloat):
|
275
|
-
raise TypeError("Can only add FlexFloat instances.")
|
276
|
-
|
277
|
-
if self.sign != other.sign:
|
278
|
-
return self - (-other)
|
279
|
-
|
280
|
-
# OBJECTIVE: Add two FlexFloat instances together.
|
281
|
-
# https://www.sciencedirect.com/topics/computer-science/floating-point-addition
|
282
|
-
# and: https://cse.hkust.edu.hk/~cktang/cs180/notes/lec21.pdf
|
283
|
-
#
|
284
|
-
# Steps:
|
285
|
-
# 0. Handle special cases (NaN, Infinity).
|
286
|
-
# 1. Extract exponent and fraction bits.
|
287
|
-
# 2. Prepend leading 1 to form the mantissa.
|
288
|
-
# 3. Compare exponents.
|
289
|
-
# 4. Shift smaller mantissa if necessary.
|
290
|
-
# 5. Add mantissas.
|
291
|
-
# 6. Normalize mantissa and adjust exponent if necessary.
|
292
|
-
# 7. Grow exponent if necessary.
|
293
|
-
# 8. Round result.
|
294
|
-
# 9. Return new FlexFloat instance.
|
295
|
-
|
296
|
-
# Step 0: Handle special cases
|
297
|
-
if self.is_zero() or other.is_zero():
|
298
|
-
return self.copy() if other.is_zero() else other.copy()
|
299
|
-
|
300
|
-
if self.is_nan() or other.is_nan():
|
301
|
-
return FlexFloat.nan()
|
302
|
-
|
303
|
-
if self.is_infinity() and other.is_infinity():
|
304
|
-
return self.copy() if self.sign == other.sign else FlexFloat.nan()
|
305
|
-
if self.is_infinity() or other.is_infinity():
|
306
|
-
return self.copy() if self.is_infinity() else other.copy()
|
307
|
-
|
308
|
-
# Step 1: Extract exponent and fraction bits
|
309
|
-
exponent_self = self.exponent.to_signed_int() + 1
|
310
|
-
exponent_other = other.exponent.to_signed_int() + 1
|
311
|
-
|
312
|
-
# Step 2: Prepend leading 1 to form the mantissa
|
313
|
-
mantissa_self = [True] + self.fraction
|
314
|
-
mantissa_other = [True] + other.fraction
|
315
|
-
|
316
|
-
# Step 3: Compare exponents (self is always larger or equal)
|
317
|
-
if exponent_self < exponent_other:
|
318
|
-
exponent_self, exponent_other = exponent_other, exponent_self
|
319
|
-
mantissa_self, mantissa_other = mantissa_other, mantissa_self
|
320
|
-
|
321
|
-
# Step 4: Shift smaller mantissa if necessary
|
322
|
-
if exponent_self > exponent_other:
|
323
|
-
shift_amount = exponent_self - exponent_other
|
324
|
-
mantissa_other = mantissa_other.shift(shift_amount)
|
325
|
-
|
326
|
-
# Step 5: Add mantissas
|
327
|
-
assert (
|
328
|
-
len(mantissa_self) == 53
|
329
|
-
), "Fraction must be 53 bits long. (1 leading bit + 52 fraction bits)"
|
330
|
-
assert len(mantissa_self) == len(mantissa_other), (
|
331
|
-
f"Mantissas must be the same length. Expected 53 bits, "
|
332
|
-
f"got {len(mantissa_other)} bits."
|
333
|
-
)
|
334
|
-
|
335
|
-
mantissa_result = BitArray.zeros(53) # 1 leading bit + 52 fraction bits
|
336
|
-
carry = False
|
337
|
-
for i in range(52, -1, -1):
|
338
|
-
total = mantissa_self[i] + mantissa_other[i] + carry
|
339
|
-
mantissa_result[i] = total % 2 == 1
|
340
|
-
carry = total > 1
|
341
|
-
|
342
|
-
# Step 6: Normalize mantissa and adjust exponent if necessary
|
343
|
-
# Only need to normalize if there is a carry
|
344
|
-
if carry:
|
345
|
-
# Insert the carry bit and shift right
|
346
|
-
mantissa_result = mantissa_result.shift(1, fill=True)
|
347
|
-
exponent_self += 1
|
348
|
-
|
349
|
-
# Step 7: Grow exponent if necessary
|
350
|
-
exp_result_length = self._grow_exponent(exponent_self, len(self.exponent))
|
351
|
-
assert (
|
352
|
-
exponent_self - (1 << (exp_result_length - 1)) < 2
|
353
|
-
), "Exponent growth should not exceed 1 bit."
|
354
|
-
|
355
|
-
exponent_result = BitArray.from_signed_int(exponent_self - 1, exp_result_length)
|
356
|
-
return FlexFloat(
|
357
|
-
sign=self.sign,
|
358
|
-
exponent=exponent_result,
|
359
|
-
fraction=mantissa_result[1:], # Exclude leading bit
|
360
|
-
)
|
361
|
-
|
362
|
-
def __sub__(self, other: FlexFloat | Number) -> FlexFloat:
|
363
|
-
"""Subtract one FlexFloat instance from another.
|
364
|
-
|
365
|
-
Args:
|
366
|
-
other (FlexFloat | float | int): The FlexFloat instance to subtract.
|
367
|
-
Returns:
|
368
|
-
FlexFloat: A new FlexFloat instance representing the difference.
|
369
|
-
"""
|
370
|
-
if isinstance(other, Number):
|
371
|
-
other = FlexFloat.from_float(other)
|
372
|
-
if not isinstance(other, FlexFloat):
|
373
|
-
raise TypeError("Can only subtract FlexFloat instances.")
|
374
|
-
|
375
|
-
# If signs are different, subtraction becomes addition
|
376
|
-
if self.sign != other.sign:
|
377
|
-
return self + (-other)
|
378
|
-
|
379
|
-
# OBJECTIVE: Subtract two FlexFloat instances.
|
380
|
-
# Based on floating-point subtraction algorithms
|
381
|
-
#
|
382
|
-
# Steps:
|
383
|
-
# 0. Handle special cases (NaN, Infinity, zero).
|
384
|
-
# 1. Extract exponent and fraction bits.
|
385
|
-
# 2. Prepend leading 1 to form the mantissa.
|
386
|
-
# 3. Compare exponents and align mantissas.
|
387
|
-
# 4. Compare magnitudes to determine result sign.
|
388
|
-
# 5. Subtract mantissas (larger - smaller).
|
389
|
-
# 6. Normalize mantissa and adjust exponent if necessary.
|
390
|
-
# 7. Grow exponent if necessary.
|
391
|
-
# 8. Return new FlexFloat instance.
|
392
|
-
|
393
|
-
# Step 0: Handle special cases
|
394
|
-
if self.is_zero() or other.is_zero():
|
395
|
-
return self.copy() if other.is_zero() else -other.copy()
|
396
|
-
|
397
|
-
if self.is_nan() or other.is_nan():
|
398
|
-
return FlexFloat.nan()
|
399
|
-
|
400
|
-
if self.is_infinity() and other.is_infinity():
|
401
|
-
if self.sign == other.sign:
|
402
|
-
return FlexFloat.nan() # inf - inf = NaN
|
403
|
-
return self.copy() # inf - (-inf) = inf
|
404
|
-
|
405
|
-
if self.is_infinity():
|
406
|
-
return self.copy()
|
407
|
-
|
408
|
-
if other.is_infinity():
|
409
|
-
return -other
|
410
|
-
|
411
|
-
# Step 1: Extract exponent and fraction bits
|
412
|
-
exponent_self = self.exponent.to_signed_int() + 1
|
413
|
-
exponent_other = other.exponent.to_signed_int() + 1
|
414
|
-
|
415
|
-
# Step 2: Prepend leading 1 to form the mantissa
|
416
|
-
mantissa_self = [True] + self.fraction
|
417
|
-
mantissa_other = [True] + other.fraction
|
418
|
-
|
419
|
-
# Step 3: Align mantissas by shifting the smaller exponent
|
420
|
-
result_sign = self.sign
|
421
|
-
shift_amount = abs(exponent_self - exponent_other)
|
422
|
-
if exponent_self >= exponent_other:
|
423
|
-
mantissa_other = mantissa_other.shift(shift_amount)
|
424
|
-
result_exponent = exponent_self
|
425
|
-
else:
|
426
|
-
mantissa_self = mantissa_self.shift(shift_amount)
|
427
|
-
result_exponent = exponent_other
|
428
|
-
|
429
|
-
# Step 4: Compare magnitudes to determine which mantissa is larger
|
430
|
-
# Convert mantissas to integers for comparison
|
431
|
-
mantissa_self_int = mantissa_self.to_int()
|
432
|
-
mantissa_other_int = mantissa_other.to_int()
|
433
|
-
|
434
|
-
if mantissa_self_int >= mantissa_other_int:
|
435
|
-
larger_mantissa = mantissa_self
|
436
|
-
smaller_mantissa = mantissa_other
|
437
|
-
result_sign = self.sign
|
438
|
-
else:
|
439
|
-
larger_mantissa = mantissa_other
|
440
|
-
smaller_mantissa = mantissa_self
|
441
|
-
# Flip sign since we're computing -(smaller - larger)
|
442
|
-
result_sign = not self.sign
|
443
|
-
|
444
|
-
# Step 5: Subtract mantissas (larger - smaller)
|
445
|
-
assert (
|
446
|
-
len(larger_mantissa) == 53
|
447
|
-
), "Mantissa must be 53 bits long. (1 leading bit + 52 fraction bits)"
|
448
|
-
assert len(larger_mantissa) == len(smaller_mantissa), (
|
449
|
-
f"Mantissas must be the same length. Expected 53 bits, "
|
450
|
-
f"got {len(smaller_mantissa)} bits."
|
451
|
-
)
|
452
|
-
|
453
|
-
mantissa_result = BitArray.zeros(53)
|
454
|
-
borrow = False
|
455
|
-
for i in range(52, -1, -1):
|
456
|
-
diff = int(larger_mantissa[i]) - int(smaller_mantissa[i]) - int(borrow)
|
457
|
-
|
458
|
-
mantissa_result[i] = diff % 2 == 1
|
459
|
-
borrow = diff < 0
|
460
|
-
|
461
|
-
assert not borrow, "Subtraction should not result in a negative mantissa."
|
462
|
-
|
463
|
-
# Step 6: Normalize mantissa and adjust exponent if necessary
|
464
|
-
# Find the first 1 bit (leading bit might have been canceled out)
|
465
|
-
leading_zero_count = next(
|
466
|
-
(i for i, bit in enumerate(mantissa_result) if bit), len(mantissa_result)
|
467
|
-
)
|
468
|
-
|
469
|
-
# Handle case where result becomes zero or denormalized
|
470
|
-
if leading_zero_count >= 53:
|
471
|
-
return FlexFloat.from_float(0.0)
|
472
|
-
|
473
|
-
if leading_zero_count > 0:
|
474
|
-
# Shift left to normalize
|
475
|
-
mantissa_result = mantissa_result.shift(-leading_zero_count)
|
476
|
-
result_exponent -= leading_zero_count
|
477
|
-
|
478
|
-
# Step 7: Grow exponent if necessary (handle underflow)
|
479
|
-
exp_result_length = self._grow_exponent(result_exponent, len(self.exponent))
|
480
|
-
|
481
|
-
exp_result = BitArray.from_signed_int(result_exponent - 1, exp_result_length)
|
482
|
-
|
483
|
-
return FlexFloat(
|
484
|
-
sign=result_sign,
|
485
|
-
exponent=exp_result,
|
486
|
-
fraction=mantissa_result[1:], # Exclude leading bit
|
487
|
-
)
|
488
|
-
|
489
|
-
def __mul__(self, other: FlexFloat | Number) -> FlexFloat:
|
490
|
-
"""Multiply two FlexFloat instances together.
|
491
|
-
|
492
|
-
Args:
|
493
|
-
other (FlexFloat | float | int): The other FlexFloat instance to multiply.
|
494
|
-
Returns:
|
495
|
-
FlexFloat: A new FlexFloat instance representing the product.
|
496
|
-
"""
|
497
|
-
if isinstance(other, Number):
|
498
|
-
other = FlexFloat.from_float(other)
|
499
|
-
if not isinstance(other, FlexFloat):
|
500
|
-
raise TypeError("Can only multiply FlexFloat instances.")
|
501
|
-
|
502
|
-
# OBJECTIVE: Multiply two FlexFloat instances together.
|
503
|
-
# https://www.rfwireless-world.com/tutorials/ieee-754-floating-point-arithmetic
|
504
|
-
#
|
505
|
-
# Steps:
|
506
|
-
# 0. Handle special cases (NaN, Infinity, zero).
|
507
|
-
# 1. Calculate result sign (XOR of operand signs).
|
508
|
-
# 2. Extract and add exponents (subtract bias).
|
509
|
-
# 3. Multiply mantissas.
|
510
|
-
# 4. Normalize mantissa and adjust exponent if necessary.
|
511
|
-
# 5. Check for overflow/underflow.
|
512
|
-
# 6. Grow exponent if necessary.
|
513
|
-
# 7. Return new FlexFloat instance.
|
514
|
-
|
515
|
-
# Step 0: Handle special cases
|
516
|
-
if self.is_nan() or other.is_nan():
|
517
|
-
return FlexFloat.nan()
|
518
|
-
|
519
|
-
if self.is_zero() or other.is_zero():
|
520
|
-
return FlexFloat.zero()
|
521
|
-
|
522
|
-
if self.is_infinity() or other.is_infinity():
|
523
|
-
result_sign = self.sign ^ other.sign
|
524
|
-
return FlexFloat.infinity(sign=result_sign)
|
525
|
-
|
526
|
-
# Step 1: Calculate result sign (XOR of signs)
|
527
|
-
result_sign = self.sign ^ other.sign
|
528
|
-
|
529
|
-
# Step 2: Extract exponent and fraction bits
|
530
|
-
# Note: The stored exponent needs +1 to get the actual value (like in addition)
|
531
|
-
exponent_self = self.exponent.to_signed_int() + 1
|
532
|
-
exponent_other = other.exponent.to_signed_int() + 1
|
533
|
-
|
534
|
-
# Step 3: Add exponents
|
535
|
-
# When multiplying, we add the unbiased exponents
|
536
|
-
result_exponent = exponent_self + exponent_other
|
537
|
-
|
538
|
-
# Step 4: Multiply mantissas
|
539
|
-
# Prepend leading 1 to form the mantissa (1.fraction)
|
540
|
-
mantissa_self = [True] + self.fraction
|
541
|
-
mantissa_other = [True] + other.fraction
|
542
|
-
|
543
|
-
# Convert mantissas to integers for multiplication
|
544
|
-
mantissa_self_int = mantissa_self.to_int()
|
545
|
-
mantissa_other_int = mantissa_other.to_int()
|
546
|
-
|
547
|
-
# Multiply the mantissas
|
548
|
-
product = mantissa_self_int * mantissa_other_int
|
549
|
-
|
550
|
-
# Convert back to bit array
|
551
|
-
# The product will have up to 106 bits (53 + 53)
|
552
|
-
if product == 0:
|
553
|
-
return FlexFloat.zero()
|
554
|
-
|
555
|
-
product_bits = BitArray.zeros(106)
|
556
|
-
for i in range(105, -1, -1):
|
557
|
-
product_bits[i] = product & 1 == 1
|
558
|
-
product >>= 1
|
559
|
-
if product <= 0:
|
560
|
-
break
|
561
|
-
|
562
|
-
# Step 5: Normalize mantissa and adjust exponent if necessary
|
563
|
-
# Find the position of the most significant bit
|
564
|
-
msb_position = next((i for i, bit in enumerate(product_bits) if bit), None)
|
565
|
-
|
566
|
-
assert msb_position is not None, "Product should not be zero here."
|
567
|
-
|
568
|
-
# The mantissa multiplication gives us a result with 2 integer bits
|
569
|
-
# We need to normalize to have exactly 1 integer bit
|
570
|
-
# If MSB is at position 0, we have a 2-bit integer part (11.xxxxx)
|
571
|
-
# If MSB is at position 1, we have a 1-bit integer part (1.xxxxx)
|
572
|
-
if msb_position == 0:
|
573
|
-
result_exponent += 1
|
574
|
-
normalized_mantissa = product_bits[msb_position : msb_position + 53]
|
575
|
-
|
576
|
-
# Pad with zeros if we don't have enough bits
|
577
|
-
missing_bits = 53 - len(normalized_mantissa)
|
578
|
-
if missing_bits > 0:
|
579
|
-
normalized_mantissa += [False] * missing_bits
|
580
|
-
|
581
|
-
# Step 6: Grow exponent if necessary to accommodate the result
|
582
|
-
exp_result_length = max(len(self.exponent), len(other.exponent))
|
583
|
-
|
584
|
-
# Check if we need to grow the exponent to accommodate the result
|
585
|
-
exp_result_length = self._grow_exponent(result_exponent, exp_result_length)
|
586
|
-
|
587
|
-
exp_result = BitArray.from_signed_int(result_exponent - 1, exp_result_length)
|
588
|
-
|
589
|
-
return FlexFloat(
|
590
|
-
sign=result_sign,
|
591
|
-
exponent=exp_result,
|
592
|
-
fraction=normalized_mantissa[1:], # Exclude leading bit
|
593
|
-
)
|
594
|
-
|
595
|
-
def __rmul__(self, other: Number) -> FlexFloat:
|
596
|
-
"""Right-hand multiplication for Number types.
|
597
|
-
|
598
|
-
Args:
|
599
|
-
other (float | int): The number to multiply with this FlexFloat.
|
600
|
-
Returns:
|
601
|
-
FlexFloat: A new FlexFloat instance representing the product.
|
602
|
-
"""
|
603
|
-
return self * other
|
604
|
-
|
605
|
-
def __truediv__(self, other: FlexFloat | Number) -> FlexFloat:
|
606
|
-
"""Divide this FlexFloat by another FlexFloat or number.
|
607
|
-
|
608
|
-
Args:
|
609
|
-
other (FlexFloat | float | int): The divisor.
|
610
|
-
Returns:
|
611
|
-
FlexFloat: A new FlexFloat instance representing the quotient.
|
612
|
-
"""
|
613
|
-
if isinstance(other, Number):
|
614
|
-
other = FlexFloat.from_float(other)
|
615
|
-
if not isinstance(other, FlexFloat):
|
616
|
-
raise TypeError("Can only divide FlexFloat instances.")
|
617
|
-
|
618
|
-
# OBJECTIVE: Divide two FlexFloat instances.
|
619
|
-
# https://www.rfwireless-world.com/tutorials/ieee-754-floating-point-arithmetic
|
620
|
-
#
|
621
|
-
# Steps:
|
622
|
-
# 0. Handle special cases (NaN, Infinity, zero).
|
623
|
-
# 1. Calculate result sign (XOR of operand signs).
|
624
|
-
# 2. Extract and subtract exponents (add bias).
|
625
|
-
# 3. Divide mantissas.
|
626
|
-
# 4. Normalize mantissa and adjust exponent if necessary.
|
627
|
-
# 5. Check for overflow/underflow.
|
628
|
-
# 6. Grow exponent if necessary.
|
629
|
-
# 7. Return new FlexFloat instance.
|
630
|
-
|
631
|
-
# Step 0: Handle special cases
|
632
|
-
if self.is_nan() or other.is_nan():
|
633
|
-
return FlexFloat.nan()
|
634
|
-
|
635
|
-
# Zero cases
|
636
|
-
if self.is_zero() and other.is_zero():
|
637
|
-
return FlexFloat.nan() # 0 / 0 = NaN
|
638
|
-
if self.is_zero() and not other.is_zero():
|
639
|
-
return FlexFloat.zero() # 0 / finite = 0
|
640
|
-
if not self.is_zero() and other.is_zero():
|
641
|
-
return FlexFloat.infinity(sign=self.sign ^ other.sign) # finite / 0 = inf
|
642
|
-
|
643
|
-
# Infinity cases
|
644
|
-
if self.is_infinity() and other.is_infinity():
|
645
|
-
return FlexFloat.nan() # inf / inf = NaN
|
646
|
-
if self.is_infinity():
|
647
|
-
return FlexFloat.infinity(sign=self.sign ^ other.sign) # inf / finite = inf
|
648
|
-
if other.is_infinity():
|
649
|
-
return FlexFloat.zero() # finite / inf = 0
|
650
|
-
|
651
|
-
# Step 1: Calculate result sign (XOR of signs)
|
652
|
-
result_sign = self.sign ^ other.sign
|
653
|
-
|
654
|
-
# Step 2: Extract exponent and fraction bits
|
655
|
-
# Note: The stored exponent needs +1 to get the actual value
|
656
|
-
# (like in multiplication)
|
657
|
-
exponent_self = self.exponent.to_signed_int() + 1
|
658
|
-
exponent_other = other.exponent.to_signed_int() + 1
|
659
|
-
|
660
|
-
# Step 3: Subtract exponents (for division, we subtract the divisor's exponent)
|
661
|
-
result_exponent = exponent_self - exponent_other
|
662
|
-
|
663
|
-
# Step 4: Divide mantissas
|
664
|
-
# Prepend leading 1 to form the mantissa (1.fraction)
|
665
|
-
mantissa_self = [True] + self.fraction
|
666
|
-
mantissa_other = [True] + other.fraction
|
667
|
-
|
668
|
-
# Convert mantissas to integers for division
|
669
|
-
mantissa_self_int = mantissa_self.to_int()
|
670
|
-
mantissa_other_int = mantissa_other.to_int()
|
671
|
-
|
672
|
-
# Normalize mantissa for division (avoid overflow) -> scale the dividend
|
673
|
-
if mantissa_self_int < mantissa_other_int:
|
674
|
-
scale_factor = 1 << 53
|
675
|
-
result_exponent -= 1 # Adjust exponent since result < 1.0
|
676
|
-
else:
|
677
|
-
scale_factor = 1 << 52
|
678
|
-
scaled_dividend = mantissa_self_int * scale_factor
|
679
|
-
quotient = scaled_dividend // mantissa_other_int
|
680
|
-
|
681
|
-
if quotient == 0:
|
682
|
-
return FlexFloat.zero()
|
683
|
-
|
684
|
-
# Convert quotient to BitArray for easier bit manipulation
|
685
|
-
quotient_bitarray = BitArray.zeros(64) # Use a fixed size for consistency
|
686
|
-
temp_quotient = quotient
|
687
|
-
bit_pos = 63
|
688
|
-
while temp_quotient > 0 and bit_pos >= 0:
|
689
|
-
quotient_bitarray[bit_pos] = (temp_quotient & 1) == 1
|
690
|
-
temp_quotient >>= 1
|
691
|
-
bit_pos -= 1
|
692
|
-
|
693
|
-
# Step 5: Normalize mantissa and adjust exponent if necessary
|
694
|
-
# Find the position of the most significant bit (first 1)
|
695
|
-
msb_pos = next((i for i, bit in enumerate(quotient_bitarray) if bit), None)
|
696
|
-
|
697
|
-
if msb_pos is None:
|
698
|
-
return FlexFloat.zero()
|
699
|
-
|
700
|
-
# Extract exactly 53 bits starting from the MSB (1 integer + 52 fraction)
|
701
|
-
normalized_mantissa = quotient_bitarray[msb_pos : msb_pos + 53]
|
702
|
-
normalized_mantissa = normalized_mantissa.shift(
|
703
|
-
53 - len(normalized_mantissa), fill=False
|
704
|
-
)
|
705
|
-
|
706
|
-
# Step 6: Grow exponent if necessary to accommodate the result
|
707
|
-
exp_result_length = max(len(self.exponent), len(other.exponent))
|
708
|
-
|
709
|
-
# Check if we need to grow the exponent to accommodate the result
|
710
|
-
exp_result_length = self._grow_exponent(result_exponent, exp_result_length)
|
711
|
-
|
712
|
-
exp_result = BitArray.from_signed_int(result_exponent - 1, exp_result_length)
|
713
|
-
|
714
|
-
return FlexFloat(
|
715
|
-
sign=result_sign,
|
716
|
-
exponent=exp_result,
|
717
|
-
fraction=normalized_mantissa[1:], # Exclude leading bit
|
718
|
-
)
|
719
|
-
|
720
|
-
def __rtruediv__(self, other: Number) -> FlexFloat:
|
721
|
-
"""Right-hand division for Number types.
|
722
|
-
|
723
|
-
Args:
|
724
|
-
other (float | int): The number to divide by this FlexFloat.
|
725
|
-
Returns:
|
726
|
-
FlexFloat: A new FlexFloat instance representing the quotient.
|
727
|
-
"""
|
728
|
-
return FlexFloat.from_float(other) / self
|
1
|
+
"""Core FlexFloat class implementation."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import math
|
6
|
+
from typing import Final
|
7
|
+
|
8
|
+
from .bitarray import BitArray
|
9
|
+
from .types import Number
|
10
|
+
|
11
|
+
LOG10_2: Final[float] = math.log10(2)
|
12
|
+
|
13
|
+
|
14
|
+
class FlexFloat:
|
15
|
+
"""A class to represent a floating-point number with growable exponent and
|
16
|
+
fixed-size fraction. This class is designed to handle very large or very
|
17
|
+
small numbers by adjusting the exponent dynamically. While keeping the
|
18
|
+
mantissa (fraction) fixed in size.
|
19
|
+
|
20
|
+
This class follows the IEEE 754 double-precision floating-point format,
|
21
|
+
but extends it to allow for a growable exponent and a fixed-size fraction.
|
22
|
+
|
23
|
+
Attributes:
|
24
|
+
sign (bool): The sign of the number (True for negative, False for positive).
|
25
|
+
exponent (BitArray): A growable bit array representing the exponent
|
26
|
+
(uses off-set binary representation).
|
27
|
+
fraction (BitArray): A fixed-size bit array representing the fraction
|
28
|
+
(mantissa) of the number.
|
29
|
+
"""
|
30
|
+
|
31
|
+
def __init__(
|
32
|
+
self,
|
33
|
+
sign: bool = False,
|
34
|
+
exponent: BitArray | None = None,
|
35
|
+
fraction: BitArray | None = None,
|
36
|
+
):
|
37
|
+
"""Initialize a FlexFloat instance.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
sign (bool): The sign of the number (True for negative, False for positive).
|
41
|
+
exponent (BitArray | None): The exponent bit array (If None, represents 0).
|
42
|
+
fraction (BitArray | None): The fraction bit array (If None, represents 0).
|
43
|
+
"""
|
44
|
+
self.sign = sign
|
45
|
+
self.exponent = exponent if exponent is not None else BitArray.zeros(11)
|
46
|
+
self.fraction = fraction if fraction is not None else BitArray.zeros(52)
|
47
|
+
|
48
|
+
@classmethod
|
49
|
+
def from_float(cls, value: Number) -> FlexFloat:
|
50
|
+
"""Create a FlexFloat instance from a number.
|
51
|
+
|
52
|
+
Args:
|
53
|
+
value (Number): The number to convert to FlexFloat.
|
54
|
+
Returns:
|
55
|
+
FlexFloat: A new FlexFloat instance representing the number.
|
56
|
+
"""
|
57
|
+
value = float(value)
|
58
|
+
bits = BitArray.from_float(value)
|
59
|
+
|
60
|
+
return cls(sign=bits[0], exponent=bits[1:12], fraction=bits[12:64])
|
61
|
+
|
62
|
+
def to_float(self) -> float:
|
63
|
+
"""Convert the FlexFloat instance back to a 64-bit float.
|
64
|
+
|
65
|
+
If float is bigger than 64 bits, it will truncate the value to fit.
|
66
|
+
|
67
|
+
Returns:
|
68
|
+
float: The floating-point number represented by the FlexFloat instance.
|
69
|
+
Raises:
|
70
|
+
ValueError: If the exponent or fraction lengths are not as expected.
|
71
|
+
"""
|
72
|
+
if len(self.exponent) < 11 or len(self.fraction) < 52:
|
73
|
+
raise ValueError("Must be a standard 64-bit FlexFloat")
|
74
|
+
|
75
|
+
bits = BitArray([self.sign]) + self.exponent[:11] + self.fraction[:52]
|
76
|
+
return bits.to_float()
|
77
|
+
|
78
|
+
def __repr__(self) -> str:
|
79
|
+
"""Return a string representation of the FlexFloat instance.
|
80
|
+
|
81
|
+
Returns:
|
82
|
+
str: A string representation of the FlexFloat instance.
|
83
|
+
"""
|
84
|
+
return (
|
85
|
+
"FlexFloat("
|
86
|
+
f"sign={self.sign}, "
|
87
|
+
f"exponent={self.exponent}, "
|
88
|
+
f"fraction={self.fraction})"
|
89
|
+
)
|
90
|
+
|
91
|
+
def pretty(self) -> str:
|
92
|
+
"""Return an easier to read string representation of the FlexFloat instance.
|
93
|
+
Mainly converts the exponent and fraction to integers for readability.
|
94
|
+
|
95
|
+
Returns:
|
96
|
+
str: A pretty string representation of the FlexFloat instance.
|
97
|
+
"""
|
98
|
+
sign = "-" if self.sign else ""
|
99
|
+
exponent_value = self.exponent.to_signed_int() + 1
|
100
|
+
fraction_value = self.fraction.to_int()
|
101
|
+
return f"{sign}FlexFloat(exponent={exponent_value}, fraction={fraction_value})"
|
102
|
+
|
103
|
+
@classmethod
|
104
|
+
def nan(cls) -> FlexFloat:
|
105
|
+
"""Create a FlexFloat instance representing NaN (Not a Number).
|
106
|
+
|
107
|
+
Returns:
|
108
|
+
FlexFloat: A new FlexFloat instance representing NaN.
|
109
|
+
"""
|
110
|
+
exponent = BitArray.ones(11)
|
111
|
+
fraction = BitArray.ones(52)
|
112
|
+
return cls(sign=True, exponent=exponent, fraction=fraction)
|
113
|
+
|
114
|
+
@classmethod
|
115
|
+
def infinity(cls, sign: bool = False) -> FlexFloat:
|
116
|
+
"""Create a FlexFloat instance representing Infinity.
|
117
|
+
|
118
|
+
Args:
|
119
|
+
sign (bool): Indicates if the infinity is negative.
|
120
|
+
Returns:
|
121
|
+
FlexFloat: A new FlexFloat instance representing Infinity.
|
122
|
+
"""
|
123
|
+
exponent = BitArray.ones(11)
|
124
|
+
fraction = BitArray.zeros(52)
|
125
|
+
return cls(sign=sign, exponent=exponent, fraction=fraction)
|
126
|
+
|
127
|
+
@classmethod
|
128
|
+
def zero(cls) -> FlexFloat:
|
129
|
+
"""Create a FlexFloat instance representing zero.
|
130
|
+
|
131
|
+
Returns:
|
132
|
+
FlexFloat: A new FlexFloat instance representing zero.
|
133
|
+
"""
|
134
|
+
exponent = BitArray.zeros(11)
|
135
|
+
fraction = BitArray.zeros(52)
|
136
|
+
return cls(sign=False, exponent=exponent, fraction=fraction)
|
137
|
+
|
138
|
+
def _is_special_exponent(self) -> bool:
|
139
|
+
"""Check if the exponent represents a special value (NaN or Infinity).
|
140
|
+
|
141
|
+
Returns:
|
142
|
+
bool: True if the exponent is at its maximum value, False otherwise.
|
143
|
+
"""
|
144
|
+
# In IEEE 754, special values have all exponent bits set to 1
|
145
|
+
# This corresponds to the maximum value in the unsigned representation
|
146
|
+
# For signed offset binary, the maximum value is 2^(n-1) - 1
|
147
|
+
# where n is the number of bits
|
148
|
+
max_signed_value = (1 << (len(self.exponent) - 1)) - 1
|
149
|
+
return self.exponent.to_signed_int() == max_signed_value
|
150
|
+
|
151
|
+
def is_nan(self) -> bool:
|
152
|
+
"""Check if the FlexFloat instance represents NaN (Not a Number).
|
153
|
+
|
154
|
+
Returns:
|
155
|
+
bool: True if the FlexFloat instance is NaN, False otherwise.
|
156
|
+
"""
|
157
|
+
return self._is_special_exponent() and any(self.fraction)
|
158
|
+
|
159
|
+
def is_infinity(self) -> bool:
|
160
|
+
"""Check if the FlexFloat instance represents Infinity.
|
161
|
+
|
162
|
+
Returns:
|
163
|
+
bool: True if the FlexFloat instance is Infinity, False otherwise.
|
164
|
+
"""
|
165
|
+
return self._is_special_exponent() and not any(self.fraction)
|
166
|
+
|
167
|
+
def is_zero(self) -> bool:
|
168
|
+
"""Check if the FlexFloat instance represents zero.
|
169
|
+
|
170
|
+
Returns:
|
171
|
+
bool: True if the FlexFloat instance is zero, False otherwise.
|
172
|
+
"""
|
173
|
+
return not any(self.exponent) and not any(self.fraction)
|
174
|
+
|
175
|
+
def copy(self) -> FlexFloat:
|
176
|
+
"""Create a copy of the FlexFloat instance.
|
177
|
+
|
178
|
+
Returns:
|
179
|
+
FlexFloat: A new FlexFloat instance with the same data as the original.
|
180
|
+
"""
|
181
|
+
return FlexFloat(
|
182
|
+
sign=self.sign, exponent=self.exponent.copy(), fraction=self.fraction.copy()
|
183
|
+
)
|
184
|
+
|
185
|
+
def __str__(self) -> str:
|
186
|
+
"""Float representation of the FlexFloat using a generic algorithm.
|
187
|
+
|
188
|
+
This implementation doesn't rely on Python's float conversion and instead
|
189
|
+
implements the formatting logic directly, making it work for any exponent size.
|
190
|
+
|
191
|
+
Currently, it only operates in scientific notation with 5 decimal places.
|
192
|
+
"""
|
193
|
+
sign_str = "-" if self.sign else ""
|
194
|
+
# Handle special cases first
|
195
|
+
if self.is_nan():
|
196
|
+
return "nan"
|
197
|
+
|
198
|
+
if self.is_infinity():
|
199
|
+
return f"{sign_str}inf"
|
200
|
+
|
201
|
+
if self.is_zero():
|
202
|
+
return f"{sign_str}0.00000e+00"
|
203
|
+
|
204
|
+
exponent = self.exponent.to_signed_int() + 1
|
205
|
+
|
206
|
+
# Convert fraction to decimal value between 1 and 2
|
207
|
+
# (starting with 1.0 for the implicit leading bit)
|
208
|
+
mantissa = 1.0
|
209
|
+
for i, bit in enumerate(self.fraction):
|
210
|
+
if bit:
|
211
|
+
mantissa += 1.0 / (1 << (i + 1))
|
212
|
+
|
213
|
+
# To avoid overflow with very large exponents, work in log space
|
214
|
+
# log10(mantissa * 2^exponent) = log10(mantissa) + exponent * log10(2)
|
215
|
+
log10_mantissa = math.log10(mantissa)
|
216
|
+
log10_total = log10_mantissa + exponent * LOG10_2
|
217
|
+
|
218
|
+
decimal_exponent = int(log10_total)
|
219
|
+
|
220
|
+
log10_normalized = log10_total - decimal_exponent
|
221
|
+
normalized_mantissa = math.pow(10, log10_normalized)
|
222
|
+
|
223
|
+
# Ensure the mantissa is properly normalized (between 1.0 and 10.0)
|
224
|
+
while normalized_mantissa >= 10.0:
|
225
|
+
normalized_mantissa /= 10.0
|
226
|
+
decimal_exponent += 1
|
227
|
+
while normalized_mantissa < 1.0:
|
228
|
+
normalized_mantissa *= 10.0
|
229
|
+
decimal_exponent -= 1
|
230
|
+
|
231
|
+
# Format with 5 decimal places
|
232
|
+
return f"{sign_str}{normalized_mantissa:.5f}e{decimal_exponent:+03d}"
|
233
|
+
|
234
|
+
def __neg__(self) -> FlexFloat:
|
235
|
+
"""Negate the FlexFloat instance."""
|
236
|
+
return FlexFloat(
|
237
|
+
sign=not self.sign,
|
238
|
+
exponent=self.exponent.copy(),
|
239
|
+
fraction=self.fraction.copy(),
|
240
|
+
)
|
241
|
+
|
242
|
+
@staticmethod
|
243
|
+
def _grow_exponent(exponent: int, exponent_length: int) -> int:
|
244
|
+
"""Grow the exponent if it exceeds the maximum value for the current length.
|
245
|
+
|
246
|
+
Args:
|
247
|
+
exponent (int): The current exponent value.
|
248
|
+
exponent_length (int): The current length of the exponent in bits.
|
249
|
+
Returns:
|
250
|
+
int: The new exponent length if it needs to be grown, otherwise the same
|
251
|
+
length.
|
252
|
+
"""
|
253
|
+
while True:
|
254
|
+
half = 1 << (exponent_length - 1)
|
255
|
+
min_exponent = -half
|
256
|
+
max_exponent = half - 1
|
257
|
+
|
258
|
+
if min_exponent <= exponent <= max_exponent:
|
259
|
+
break
|
260
|
+
exponent_length += 1
|
261
|
+
|
262
|
+
return exponent_length
|
263
|
+
|
264
|
+
def __add__(self, other: FlexFloat | Number) -> FlexFloat:
|
265
|
+
"""Add two FlexFloat instances together.
|
266
|
+
|
267
|
+
Args:
|
268
|
+
other (FlexFloat | float | int): The other FlexFloat instance to add.
|
269
|
+
Returns:
|
270
|
+
FlexFloat: A new FlexFloat instance representing the sum.
|
271
|
+
"""
|
272
|
+
if isinstance(other, Number):
|
273
|
+
other = FlexFloat.from_float(other)
|
274
|
+
if not isinstance(other, FlexFloat):
|
275
|
+
raise TypeError("Can only add FlexFloat instances.")
|
276
|
+
|
277
|
+
if self.sign != other.sign:
|
278
|
+
return self - (-other)
|
279
|
+
|
280
|
+
# OBJECTIVE: Add two FlexFloat instances together.
|
281
|
+
# https://www.sciencedirect.com/topics/computer-science/floating-point-addition
|
282
|
+
# and: https://cse.hkust.edu.hk/~cktang/cs180/notes/lec21.pdf
|
283
|
+
#
|
284
|
+
# Steps:
|
285
|
+
# 0. Handle special cases (NaN, Infinity).
|
286
|
+
# 1. Extract exponent and fraction bits.
|
287
|
+
# 2. Prepend leading 1 to form the mantissa.
|
288
|
+
# 3. Compare exponents.
|
289
|
+
# 4. Shift smaller mantissa if necessary.
|
290
|
+
# 5. Add mantissas.
|
291
|
+
# 6. Normalize mantissa and adjust exponent if necessary.
|
292
|
+
# 7. Grow exponent if necessary.
|
293
|
+
# 8. Round result.
|
294
|
+
# 9. Return new FlexFloat instance.
|
295
|
+
|
296
|
+
# Step 0: Handle special cases
|
297
|
+
if self.is_zero() or other.is_zero():
|
298
|
+
return self.copy() if other.is_zero() else other.copy()
|
299
|
+
|
300
|
+
if self.is_nan() or other.is_nan():
|
301
|
+
return FlexFloat.nan()
|
302
|
+
|
303
|
+
if self.is_infinity() and other.is_infinity():
|
304
|
+
return self.copy() if self.sign == other.sign else FlexFloat.nan()
|
305
|
+
if self.is_infinity() or other.is_infinity():
|
306
|
+
return self.copy() if self.is_infinity() else other.copy()
|
307
|
+
|
308
|
+
# Step 1: Extract exponent and fraction bits
|
309
|
+
exponent_self = self.exponent.to_signed_int() + 1
|
310
|
+
exponent_other = other.exponent.to_signed_int() + 1
|
311
|
+
|
312
|
+
# Step 2: Prepend leading 1 to form the mantissa
|
313
|
+
mantissa_self = [True] + self.fraction
|
314
|
+
mantissa_other = [True] + other.fraction
|
315
|
+
|
316
|
+
# Step 3: Compare exponents (self is always larger or equal)
|
317
|
+
if exponent_self < exponent_other:
|
318
|
+
exponent_self, exponent_other = exponent_other, exponent_self
|
319
|
+
mantissa_self, mantissa_other = mantissa_other, mantissa_self
|
320
|
+
|
321
|
+
# Step 4: Shift smaller mantissa if necessary
|
322
|
+
if exponent_self > exponent_other:
|
323
|
+
shift_amount = exponent_self - exponent_other
|
324
|
+
mantissa_other = mantissa_other.shift(shift_amount)
|
325
|
+
|
326
|
+
# Step 5: Add mantissas
|
327
|
+
assert (
|
328
|
+
len(mantissa_self) == 53
|
329
|
+
), "Fraction must be 53 bits long. (1 leading bit + 52 fraction bits)"
|
330
|
+
assert len(mantissa_self) == len(mantissa_other), (
|
331
|
+
f"Mantissas must be the same length. Expected 53 bits, "
|
332
|
+
f"got {len(mantissa_other)} bits."
|
333
|
+
)
|
334
|
+
|
335
|
+
mantissa_result = BitArray.zeros(53) # 1 leading bit + 52 fraction bits
|
336
|
+
carry = False
|
337
|
+
for i in range(52, -1, -1):
|
338
|
+
total = mantissa_self[i] + mantissa_other[i] + carry
|
339
|
+
mantissa_result[i] = total % 2 == 1
|
340
|
+
carry = total > 1
|
341
|
+
|
342
|
+
# Step 6: Normalize mantissa and adjust exponent if necessary
|
343
|
+
# Only need to normalize if there is a carry
|
344
|
+
if carry:
|
345
|
+
# Insert the carry bit and shift right
|
346
|
+
mantissa_result = mantissa_result.shift(1, fill=True)
|
347
|
+
exponent_self += 1
|
348
|
+
|
349
|
+
# Step 7: Grow exponent if necessary
|
350
|
+
exp_result_length = self._grow_exponent(exponent_self, len(self.exponent))
|
351
|
+
assert (
|
352
|
+
exponent_self - (1 << (exp_result_length - 1)) < 2
|
353
|
+
), "Exponent growth should not exceed 1 bit."
|
354
|
+
|
355
|
+
exponent_result = BitArray.from_signed_int(exponent_self - 1, exp_result_length)
|
356
|
+
return FlexFloat(
|
357
|
+
sign=self.sign,
|
358
|
+
exponent=exponent_result,
|
359
|
+
fraction=mantissa_result[1:], # Exclude leading bit
|
360
|
+
)
|
361
|
+
|
362
|
+
def __sub__(self, other: FlexFloat | Number) -> FlexFloat:
|
363
|
+
"""Subtract one FlexFloat instance from another.
|
364
|
+
|
365
|
+
Args:
|
366
|
+
other (FlexFloat | float | int): The FlexFloat instance to subtract.
|
367
|
+
Returns:
|
368
|
+
FlexFloat: A new FlexFloat instance representing the difference.
|
369
|
+
"""
|
370
|
+
if isinstance(other, Number):
|
371
|
+
other = FlexFloat.from_float(other)
|
372
|
+
if not isinstance(other, FlexFloat):
|
373
|
+
raise TypeError("Can only subtract FlexFloat instances.")
|
374
|
+
|
375
|
+
# If signs are different, subtraction becomes addition
|
376
|
+
if self.sign != other.sign:
|
377
|
+
return self + (-other)
|
378
|
+
|
379
|
+
# OBJECTIVE: Subtract two FlexFloat instances.
|
380
|
+
# Based on floating-point subtraction algorithms
|
381
|
+
#
|
382
|
+
# Steps:
|
383
|
+
# 0. Handle special cases (NaN, Infinity, zero).
|
384
|
+
# 1. Extract exponent and fraction bits.
|
385
|
+
# 2. Prepend leading 1 to form the mantissa.
|
386
|
+
# 3. Compare exponents and align mantissas.
|
387
|
+
# 4. Compare magnitudes to determine result sign.
|
388
|
+
# 5. Subtract mantissas (larger - smaller).
|
389
|
+
# 6. Normalize mantissa and adjust exponent if necessary.
|
390
|
+
# 7. Grow exponent if necessary.
|
391
|
+
# 8. Return new FlexFloat instance.
|
392
|
+
|
393
|
+
# Step 0: Handle special cases
|
394
|
+
if self.is_zero() or other.is_zero():
|
395
|
+
return self.copy() if other.is_zero() else -other.copy()
|
396
|
+
|
397
|
+
if self.is_nan() or other.is_nan():
|
398
|
+
return FlexFloat.nan()
|
399
|
+
|
400
|
+
if self.is_infinity() and other.is_infinity():
|
401
|
+
if self.sign == other.sign:
|
402
|
+
return FlexFloat.nan() # inf - inf = NaN
|
403
|
+
return self.copy() # inf - (-inf) = inf
|
404
|
+
|
405
|
+
if self.is_infinity():
|
406
|
+
return self.copy()
|
407
|
+
|
408
|
+
if other.is_infinity():
|
409
|
+
return -other
|
410
|
+
|
411
|
+
# Step 1: Extract exponent and fraction bits
|
412
|
+
exponent_self = self.exponent.to_signed_int() + 1
|
413
|
+
exponent_other = other.exponent.to_signed_int() + 1
|
414
|
+
|
415
|
+
# Step 2: Prepend leading 1 to form the mantissa
|
416
|
+
mantissa_self = [True] + self.fraction
|
417
|
+
mantissa_other = [True] + other.fraction
|
418
|
+
|
419
|
+
# Step 3: Align mantissas by shifting the smaller exponent
|
420
|
+
result_sign = self.sign
|
421
|
+
shift_amount = abs(exponent_self - exponent_other)
|
422
|
+
if exponent_self >= exponent_other:
|
423
|
+
mantissa_other = mantissa_other.shift(shift_amount)
|
424
|
+
result_exponent = exponent_self
|
425
|
+
else:
|
426
|
+
mantissa_self = mantissa_self.shift(shift_amount)
|
427
|
+
result_exponent = exponent_other
|
428
|
+
|
429
|
+
# Step 4: Compare magnitudes to determine which mantissa is larger
|
430
|
+
# Convert mantissas to integers for comparison
|
431
|
+
mantissa_self_int = mantissa_self.to_int()
|
432
|
+
mantissa_other_int = mantissa_other.to_int()
|
433
|
+
|
434
|
+
if mantissa_self_int >= mantissa_other_int:
|
435
|
+
larger_mantissa = mantissa_self
|
436
|
+
smaller_mantissa = mantissa_other
|
437
|
+
result_sign = self.sign
|
438
|
+
else:
|
439
|
+
larger_mantissa = mantissa_other
|
440
|
+
smaller_mantissa = mantissa_self
|
441
|
+
# Flip sign since we're computing -(smaller - larger)
|
442
|
+
result_sign = not self.sign
|
443
|
+
|
444
|
+
# Step 5: Subtract mantissas (larger - smaller)
|
445
|
+
assert (
|
446
|
+
len(larger_mantissa) == 53
|
447
|
+
), "Mantissa must be 53 bits long. (1 leading bit + 52 fraction bits)"
|
448
|
+
assert len(larger_mantissa) == len(smaller_mantissa), (
|
449
|
+
f"Mantissas must be the same length. Expected 53 bits, "
|
450
|
+
f"got {len(smaller_mantissa)} bits."
|
451
|
+
)
|
452
|
+
|
453
|
+
mantissa_result = BitArray.zeros(53)
|
454
|
+
borrow = False
|
455
|
+
for i in range(52, -1, -1):
|
456
|
+
diff = int(larger_mantissa[i]) - int(smaller_mantissa[i]) - int(borrow)
|
457
|
+
|
458
|
+
mantissa_result[i] = diff % 2 == 1
|
459
|
+
borrow = diff < 0
|
460
|
+
|
461
|
+
assert not borrow, "Subtraction should not result in a negative mantissa."
|
462
|
+
|
463
|
+
# Step 6: Normalize mantissa and adjust exponent if necessary
|
464
|
+
# Find the first 1 bit (leading bit might have been canceled out)
|
465
|
+
leading_zero_count = next(
|
466
|
+
(i for i, bit in enumerate(mantissa_result) if bit), len(mantissa_result)
|
467
|
+
)
|
468
|
+
|
469
|
+
# Handle case where result becomes zero or denormalized
|
470
|
+
if leading_zero_count >= 53:
|
471
|
+
return FlexFloat.from_float(0.0)
|
472
|
+
|
473
|
+
if leading_zero_count > 0:
|
474
|
+
# Shift left to normalize
|
475
|
+
mantissa_result = mantissa_result.shift(-leading_zero_count)
|
476
|
+
result_exponent -= leading_zero_count
|
477
|
+
|
478
|
+
# Step 7: Grow exponent if necessary (handle underflow)
|
479
|
+
exp_result_length = self._grow_exponent(result_exponent, len(self.exponent))
|
480
|
+
|
481
|
+
exp_result = BitArray.from_signed_int(result_exponent - 1, exp_result_length)
|
482
|
+
|
483
|
+
return FlexFloat(
|
484
|
+
sign=result_sign,
|
485
|
+
exponent=exp_result,
|
486
|
+
fraction=mantissa_result[1:], # Exclude leading bit
|
487
|
+
)
|
488
|
+
|
489
|
+
def __mul__(self, other: FlexFloat | Number) -> FlexFloat:
|
490
|
+
"""Multiply two FlexFloat instances together.
|
491
|
+
|
492
|
+
Args:
|
493
|
+
other (FlexFloat | float | int): The other FlexFloat instance to multiply.
|
494
|
+
Returns:
|
495
|
+
FlexFloat: A new FlexFloat instance representing the product.
|
496
|
+
"""
|
497
|
+
if isinstance(other, Number):
|
498
|
+
other = FlexFloat.from_float(other)
|
499
|
+
if not isinstance(other, FlexFloat):
|
500
|
+
raise TypeError("Can only multiply FlexFloat instances.")
|
501
|
+
|
502
|
+
# OBJECTIVE: Multiply two FlexFloat instances together.
|
503
|
+
# https://www.rfwireless-world.com/tutorials/ieee-754-floating-point-arithmetic
|
504
|
+
#
|
505
|
+
# Steps:
|
506
|
+
# 0. Handle special cases (NaN, Infinity, zero).
|
507
|
+
# 1. Calculate result sign (XOR of operand signs).
|
508
|
+
# 2. Extract and add exponents (subtract bias).
|
509
|
+
# 3. Multiply mantissas.
|
510
|
+
# 4. Normalize mantissa and adjust exponent if necessary.
|
511
|
+
# 5. Check for overflow/underflow.
|
512
|
+
# 6. Grow exponent if necessary.
|
513
|
+
# 7. Return new FlexFloat instance.
|
514
|
+
|
515
|
+
# Step 0: Handle special cases
|
516
|
+
if self.is_nan() or other.is_nan():
|
517
|
+
return FlexFloat.nan()
|
518
|
+
|
519
|
+
if self.is_zero() or other.is_zero():
|
520
|
+
return FlexFloat.zero()
|
521
|
+
|
522
|
+
if self.is_infinity() or other.is_infinity():
|
523
|
+
result_sign = self.sign ^ other.sign
|
524
|
+
return FlexFloat.infinity(sign=result_sign)
|
525
|
+
|
526
|
+
# Step 1: Calculate result sign (XOR of signs)
|
527
|
+
result_sign = self.sign ^ other.sign
|
528
|
+
|
529
|
+
# Step 2: Extract exponent and fraction bits
|
530
|
+
# Note: The stored exponent needs +1 to get the actual value (like in addition)
|
531
|
+
exponent_self = self.exponent.to_signed_int() + 1
|
532
|
+
exponent_other = other.exponent.to_signed_int() + 1
|
533
|
+
|
534
|
+
# Step 3: Add exponents
|
535
|
+
# When multiplying, we add the unbiased exponents
|
536
|
+
result_exponent = exponent_self + exponent_other
|
537
|
+
|
538
|
+
# Step 4: Multiply mantissas
|
539
|
+
# Prepend leading 1 to form the mantissa (1.fraction)
|
540
|
+
mantissa_self = [True] + self.fraction
|
541
|
+
mantissa_other = [True] + other.fraction
|
542
|
+
|
543
|
+
# Convert mantissas to integers for multiplication
|
544
|
+
mantissa_self_int = mantissa_self.to_int()
|
545
|
+
mantissa_other_int = mantissa_other.to_int()
|
546
|
+
|
547
|
+
# Multiply the mantissas
|
548
|
+
product = mantissa_self_int * mantissa_other_int
|
549
|
+
|
550
|
+
# Convert back to bit array
|
551
|
+
# The product will have up to 106 bits (53 + 53)
|
552
|
+
if product == 0:
|
553
|
+
return FlexFloat.zero()
|
554
|
+
|
555
|
+
product_bits = BitArray.zeros(106)
|
556
|
+
for i in range(105, -1, -1):
|
557
|
+
product_bits[i] = product & 1 == 1
|
558
|
+
product >>= 1
|
559
|
+
if product <= 0:
|
560
|
+
break
|
561
|
+
|
562
|
+
# Step 5: Normalize mantissa and adjust exponent if necessary
|
563
|
+
# Find the position of the most significant bit
|
564
|
+
msb_position = next((i for i, bit in enumerate(product_bits) if bit), None)
|
565
|
+
|
566
|
+
assert msb_position is not None, "Product should not be zero here."
|
567
|
+
|
568
|
+
# The mantissa multiplication gives us a result with 2 integer bits
|
569
|
+
# We need to normalize to have exactly 1 integer bit
|
570
|
+
# If MSB is at position 0, we have a 2-bit integer part (11.xxxxx)
|
571
|
+
# If MSB is at position 1, we have a 1-bit integer part (1.xxxxx)
|
572
|
+
if msb_position == 0:
|
573
|
+
result_exponent += 1
|
574
|
+
normalized_mantissa = product_bits[msb_position : msb_position + 53]
|
575
|
+
|
576
|
+
# Pad with zeros if we don't have enough bits
|
577
|
+
missing_bits = 53 - len(normalized_mantissa)
|
578
|
+
if missing_bits > 0:
|
579
|
+
normalized_mantissa += [False] * missing_bits
|
580
|
+
|
581
|
+
# Step 6: Grow exponent if necessary to accommodate the result
|
582
|
+
exp_result_length = max(len(self.exponent), len(other.exponent))
|
583
|
+
|
584
|
+
# Check if we need to grow the exponent to accommodate the result
|
585
|
+
exp_result_length = self._grow_exponent(result_exponent, exp_result_length)
|
586
|
+
|
587
|
+
exp_result = BitArray.from_signed_int(result_exponent - 1, exp_result_length)
|
588
|
+
|
589
|
+
return FlexFloat(
|
590
|
+
sign=result_sign,
|
591
|
+
exponent=exp_result,
|
592
|
+
fraction=normalized_mantissa[1:], # Exclude leading bit
|
593
|
+
)
|
594
|
+
|
595
|
+
def __rmul__(self, other: Number) -> FlexFloat:
|
596
|
+
"""Right-hand multiplication for Number types.
|
597
|
+
|
598
|
+
Args:
|
599
|
+
other (float | int): The number to multiply with this FlexFloat.
|
600
|
+
Returns:
|
601
|
+
FlexFloat: A new FlexFloat instance representing the product.
|
602
|
+
"""
|
603
|
+
return self * other
|
604
|
+
|
605
|
+
def __truediv__(self, other: FlexFloat | Number) -> FlexFloat:
|
606
|
+
"""Divide this FlexFloat by another FlexFloat or number.
|
607
|
+
|
608
|
+
Args:
|
609
|
+
other (FlexFloat | float | int): The divisor.
|
610
|
+
Returns:
|
611
|
+
FlexFloat: A new FlexFloat instance representing the quotient.
|
612
|
+
"""
|
613
|
+
if isinstance(other, Number):
|
614
|
+
other = FlexFloat.from_float(other)
|
615
|
+
if not isinstance(other, FlexFloat):
|
616
|
+
raise TypeError("Can only divide FlexFloat instances.")
|
617
|
+
|
618
|
+
# OBJECTIVE: Divide two FlexFloat instances.
|
619
|
+
# https://www.rfwireless-world.com/tutorials/ieee-754-floating-point-arithmetic
|
620
|
+
#
|
621
|
+
# Steps:
|
622
|
+
# 0. Handle special cases (NaN, Infinity, zero).
|
623
|
+
# 1. Calculate result sign (XOR of operand signs).
|
624
|
+
# 2. Extract and subtract exponents (add bias).
|
625
|
+
# 3. Divide mantissas.
|
626
|
+
# 4. Normalize mantissa and adjust exponent if necessary.
|
627
|
+
# 5. Check for overflow/underflow.
|
628
|
+
# 6. Grow exponent if necessary.
|
629
|
+
# 7. Return new FlexFloat instance.
|
630
|
+
|
631
|
+
# Step 0: Handle special cases
|
632
|
+
if self.is_nan() or other.is_nan():
|
633
|
+
return FlexFloat.nan()
|
634
|
+
|
635
|
+
# Zero cases
|
636
|
+
if self.is_zero() and other.is_zero():
|
637
|
+
return FlexFloat.nan() # 0 / 0 = NaN
|
638
|
+
if self.is_zero() and not other.is_zero():
|
639
|
+
return FlexFloat.zero() # 0 / finite = 0
|
640
|
+
if not self.is_zero() and other.is_zero():
|
641
|
+
return FlexFloat.infinity(sign=self.sign ^ other.sign) # finite / 0 = inf
|
642
|
+
|
643
|
+
# Infinity cases
|
644
|
+
if self.is_infinity() and other.is_infinity():
|
645
|
+
return FlexFloat.nan() # inf / inf = NaN
|
646
|
+
if self.is_infinity():
|
647
|
+
return FlexFloat.infinity(sign=self.sign ^ other.sign) # inf / finite = inf
|
648
|
+
if other.is_infinity():
|
649
|
+
return FlexFloat.zero() # finite / inf = 0
|
650
|
+
|
651
|
+
# Step 1: Calculate result sign (XOR of signs)
|
652
|
+
result_sign = self.sign ^ other.sign
|
653
|
+
|
654
|
+
# Step 2: Extract exponent and fraction bits
|
655
|
+
# Note: The stored exponent needs +1 to get the actual value
|
656
|
+
# (like in multiplication)
|
657
|
+
exponent_self = self.exponent.to_signed_int() + 1
|
658
|
+
exponent_other = other.exponent.to_signed_int() + 1
|
659
|
+
|
660
|
+
# Step 3: Subtract exponents (for division, we subtract the divisor's exponent)
|
661
|
+
result_exponent = exponent_self - exponent_other
|
662
|
+
|
663
|
+
# Step 4: Divide mantissas
|
664
|
+
# Prepend leading 1 to form the mantissa (1.fraction)
|
665
|
+
mantissa_self = [True] + self.fraction
|
666
|
+
mantissa_other = [True] + other.fraction
|
667
|
+
|
668
|
+
# Convert mantissas to integers for division
|
669
|
+
mantissa_self_int = mantissa_self.to_int()
|
670
|
+
mantissa_other_int = mantissa_other.to_int()
|
671
|
+
|
672
|
+
# Normalize mantissa for division (avoid overflow) -> scale the dividend
|
673
|
+
if mantissa_self_int < mantissa_other_int:
|
674
|
+
scale_factor = 1 << 53
|
675
|
+
result_exponent -= 1 # Adjust exponent since result < 1.0
|
676
|
+
else:
|
677
|
+
scale_factor = 1 << 52
|
678
|
+
scaled_dividend = mantissa_self_int * scale_factor
|
679
|
+
quotient = scaled_dividend // mantissa_other_int
|
680
|
+
|
681
|
+
if quotient == 0:
|
682
|
+
return FlexFloat.zero()
|
683
|
+
|
684
|
+
# Convert quotient to BitArray for easier bit manipulation
|
685
|
+
quotient_bitarray = BitArray.zeros(64) # Use a fixed size for consistency
|
686
|
+
temp_quotient = quotient
|
687
|
+
bit_pos = 63
|
688
|
+
while temp_quotient > 0 and bit_pos >= 0:
|
689
|
+
quotient_bitarray[bit_pos] = (temp_quotient & 1) == 1
|
690
|
+
temp_quotient >>= 1
|
691
|
+
bit_pos -= 1
|
692
|
+
|
693
|
+
# Step 5: Normalize mantissa and adjust exponent if necessary
|
694
|
+
# Find the position of the most significant bit (first 1)
|
695
|
+
msb_pos = next((i for i, bit in enumerate(quotient_bitarray) if bit), None)
|
696
|
+
|
697
|
+
if msb_pos is None:
|
698
|
+
return FlexFloat.zero()
|
699
|
+
|
700
|
+
# Extract exactly 53 bits starting from the MSB (1 integer + 52 fraction)
|
701
|
+
normalized_mantissa = quotient_bitarray[msb_pos : msb_pos + 53]
|
702
|
+
normalized_mantissa = normalized_mantissa.shift(
|
703
|
+
53 - len(normalized_mantissa), fill=False
|
704
|
+
)
|
705
|
+
|
706
|
+
# Step 6: Grow exponent if necessary to accommodate the result
|
707
|
+
exp_result_length = max(len(self.exponent), len(other.exponent))
|
708
|
+
|
709
|
+
# Check if we need to grow the exponent to accommodate the result
|
710
|
+
exp_result_length = self._grow_exponent(result_exponent, exp_result_length)
|
711
|
+
|
712
|
+
exp_result = BitArray.from_signed_int(result_exponent - 1, exp_result_length)
|
713
|
+
|
714
|
+
return FlexFloat(
|
715
|
+
sign=result_sign,
|
716
|
+
exponent=exp_result,
|
717
|
+
fraction=normalized_mantissa[1:], # Exclude leading bit
|
718
|
+
)
|
719
|
+
|
720
|
+
def __rtruediv__(self, other: Number) -> FlexFloat:
|
721
|
+
"""Right-hand division for Number types.
|
722
|
+
|
723
|
+
Args:
|
724
|
+
other (float | int): The number to divide by this FlexFloat.
|
725
|
+
Returns:
|
726
|
+
FlexFloat: A new FlexFloat instance representing the quotient.
|
727
|
+
"""
|
728
|
+
return FlexFloat.from_float(other) / self
|