flexfloat 0.1.2__py3-none-any.whl → 0.1.5__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 +28 -5
- flexfloat/bitarray/__init__.py +31 -0
- flexfloat/bitarray/bitarray.py +298 -0
- flexfloat/bitarray/bitarray_bigint.py +305 -0
- flexfloat/bitarray/bitarray_bool.py +202 -0
- flexfloat/bitarray/bitarray_int64.py +333 -0
- flexfloat/bitarray/bitarray_mixins.py +156 -0
- flexfloat/core.py +482 -94
- flexfloat/types.py +7 -1
- flexfloat-0.1.5.dist-info/METADATA +340 -0
- flexfloat-0.1.5.dist-info/RECORD +15 -0
- flexfloat/bitarray.py +0 -257
- flexfloat-0.1.2.dist-info/METADATA +0 -147
- flexfloat-0.1.2.dist-info/RECORD +0 -10
- {flexfloat-0.1.2.dist-info → flexfloat-0.1.5.dist-info}/WHEEL +0 -0
- {flexfloat-0.1.2.dist-info → flexfloat-0.1.5.dist-info}/licenses/LICENSE +0 -0
- {flexfloat-0.1.2.dist-info → flexfloat-0.1.5.dist-info}/top_level.txt +0 -0
flexfloat/core.py
CHANGED
@@ -1,11 +1,25 @@
|
|
1
|
-
"""Core FlexFloat class implementation.
|
1
|
+
"""Core FlexFloat class implementation.
|
2
|
+
|
3
|
+
This module defines the FlexFloat class, which represents a floating-point number with a
|
4
|
+
growable exponent and a fixed-size fraction. The class is designed for arbitrary
|
5
|
+
precision floating-point arithmetic, supporting very large or small values by
|
6
|
+
dynamically adjusting the exponent size.
|
7
|
+
|
8
|
+
Example:
|
9
|
+
from flexfloat import FlexFloat
|
10
|
+
a = FlexFloat(3.14, exponent_length=10, fraction_length=20)
|
11
|
+
b = FlexFloat(2.71, exponent_length=10, fraction_length=20)
|
12
|
+
c = a + b
|
13
|
+
print(c)
|
14
|
+
# Output: FlexFloat(...)
|
15
|
+
"""
|
2
16
|
|
3
17
|
from __future__ import annotations
|
4
18
|
|
5
19
|
import math
|
6
|
-
from typing import Final
|
20
|
+
from typing import ClassVar, Final, Type
|
7
21
|
|
8
|
-
from .bitarray import BitArray
|
22
|
+
from .bitarray import BitArray, ListBoolBitArray
|
9
23
|
from .types import Number
|
10
24
|
|
11
25
|
LOG10_2: Final[float] = math.log10(2)
|
@@ -28,55 +42,98 @@ class FlexFloat:
|
|
28
42
|
(mantissa) of the number.
|
29
43
|
"""
|
30
44
|
|
45
|
+
e: ClassVar[FlexFloat]
|
46
|
+
"""The mathematical constant e as a FlexFloat instance."""
|
47
|
+
_bitarray_implementation: ClassVar[Type[BitArray]] = ListBoolBitArray
|
48
|
+
"""The BitArray implementation class used for all FlexFloat instances."""
|
49
|
+
|
50
|
+
sign: bool
|
51
|
+
"""The sign of the number (True for negative, False for positive)."""
|
52
|
+
exponent: BitArray
|
53
|
+
"""A growable bit array representing the exponent (uses off-set binary
|
54
|
+
representation)."""
|
55
|
+
fraction: BitArray
|
56
|
+
"""A fixed-size bit array representing the fraction (mantissa) of the number."""
|
57
|
+
|
58
|
+
@classmethod
|
59
|
+
def set_bitarray_implementation(cls, implementation: Type[BitArray]) -> None:
|
60
|
+
"""Set the BitArray implementation to use for all FlexFloat instances.
|
61
|
+
|
62
|
+
Args:
|
63
|
+
implementation (Type[BitArray]): The BitArray implementation class to use.
|
64
|
+
"""
|
65
|
+
cls._bitarray_implementation = implementation
|
66
|
+
|
31
67
|
def __init__(
|
32
68
|
self,
|
33
69
|
sign: bool = False,
|
34
70
|
exponent: BitArray | None = None,
|
35
71
|
fraction: BitArray | None = None,
|
36
72
|
):
|
37
|
-
"""
|
73
|
+
"""Initializes a FlexFloat instance.
|
74
|
+
|
75
|
+
BitArrays are expected to be LSB-first (least significant bit at index 0,
|
76
|
+
increasing to MSB).
|
38
77
|
|
39
78
|
Args:
|
40
|
-
sign (bool): The sign of the number (True for negative, False for
|
41
|
-
|
42
|
-
|
79
|
+
sign (bool, optional): The sign of the number (True for negative, False for
|
80
|
+
positive). Defaults to False.
|
81
|
+
exponent (BitArray | None, optional): The exponent bit array. If None,
|
82
|
+
represents 0. Defaults to None.
|
83
|
+
fraction (BitArray | None, optional): The fraction bit array. If None,
|
84
|
+
represents 0. Defaults to None.
|
43
85
|
"""
|
44
86
|
self.sign = sign
|
45
|
-
self.exponent =
|
46
|
-
|
87
|
+
self.exponent = (
|
88
|
+
exponent
|
89
|
+
if exponent is not None
|
90
|
+
else self._bitarray_implementation.zeros(11)
|
91
|
+
)
|
92
|
+
self.fraction = (
|
93
|
+
fraction
|
94
|
+
if fraction is not None
|
95
|
+
else self._bitarray_implementation.zeros(52)
|
96
|
+
)
|
47
97
|
|
48
98
|
@classmethod
|
49
99
|
def from_float(cls, value: Number) -> FlexFloat:
|
50
|
-
"""
|
100
|
+
"""Creates a FlexFloat instance from a number.
|
51
101
|
|
52
102
|
Args:
|
53
103
|
value (Number): The number to convert to FlexFloat.
|
104
|
+
|
54
105
|
Returns:
|
55
106
|
FlexFloat: A new FlexFloat instance representing the number.
|
56
107
|
"""
|
57
108
|
value = float(value)
|
58
|
-
bits =
|
109
|
+
bits = cls._bitarray_implementation.from_float(value)
|
59
110
|
|
60
|
-
return cls(sign=bits[
|
111
|
+
return cls(sign=bits[63], exponent=bits[52:63], fraction=bits[:52])
|
61
112
|
|
62
113
|
def to_float(self) -> float:
|
63
|
-
"""
|
114
|
+
"""Converts the FlexFloat instance back to a 64-bit float.
|
64
115
|
|
65
116
|
If float is bigger than 64 bits, it will truncate the value to fit.
|
66
117
|
|
67
118
|
Returns:
|
68
119
|
float: The floating-point number represented by the FlexFloat instance.
|
120
|
+
|
69
121
|
Raises:
|
70
|
-
ValueError: If the
|
122
|
+
ValueError: If the FlexFloat does not have standard 64-bit exponent and
|
123
|
+
fraction.
|
71
124
|
"""
|
72
125
|
if len(self.exponent) < 11 or len(self.fraction) < 52:
|
73
126
|
raise ValueError("Must be a standard 64-bit FlexFloat")
|
74
127
|
|
75
|
-
bits =
|
128
|
+
bits = (
|
129
|
+
self.fraction[:52]
|
130
|
+
+ self.exponent[:11]
|
131
|
+
+ self._bitarray_implementation.from_bits([self.sign])
|
132
|
+
)
|
76
133
|
return bits.to_float()
|
77
134
|
|
78
135
|
def __repr__(self) -> str:
|
79
|
-
"""
|
136
|
+
"""Returns a string representation of the FlexFloat instance.
|
80
137
|
|
81
138
|
Returns:
|
82
139
|
str: A string representation of the FlexFloat instance.
|
@@ -89,7 +146,7 @@ class FlexFloat:
|
|
89
146
|
)
|
90
147
|
|
91
148
|
def pretty(self) -> str:
|
92
|
-
"""
|
149
|
+
"""Returns an easier to read string representation of the FlexFloat instance.
|
93
150
|
Mainly converts the exponent and fraction to integers for readability.
|
94
151
|
|
95
152
|
Returns:
|
@@ -102,54 +159,52 @@ class FlexFloat:
|
|
102
159
|
|
103
160
|
@classmethod
|
104
161
|
def nan(cls) -> FlexFloat:
|
105
|
-
"""
|
162
|
+
"""Creates a FlexFloat instance representing NaN (Not a Number).
|
106
163
|
|
107
164
|
Returns:
|
108
165
|
FlexFloat: A new FlexFloat instance representing NaN.
|
109
166
|
"""
|
110
|
-
exponent =
|
111
|
-
fraction =
|
167
|
+
exponent = cls._bitarray_implementation.ones(11)
|
168
|
+
fraction = cls._bitarray_implementation.ones(52)
|
112
169
|
return cls(sign=True, exponent=exponent, fraction=fraction)
|
113
170
|
|
114
171
|
@classmethod
|
115
172
|
def infinity(cls, sign: bool = False) -> FlexFloat:
|
116
|
-
"""
|
173
|
+
"""Creates a FlexFloat instance representing Infinity.
|
117
174
|
|
118
175
|
Args:
|
119
|
-
sign (bool): Indicates if the infinity is negative.
|
176
|
+
sign (bool, optional): Indicates if the infinity is negative. Defaults to
|
177
|
+
False.
|
178
|
+
|
120
179
|
Returns:
|
121
180
|
FlexFloat: A new FlexFloat instance representing Infinity.
|
122
181
|
"""
|
123
|
-
exponent =
|
124
|
-
fraction =
|
182
|
+
exponent = cls._bitarray_implementation.ones(11)
|
183
|
+
fraction = cls._bitarray_implementation.zeros(52)
|
125
184
|
return cls(sign=sign, exponent=exponent, fraction=fraction)
|
126
185
|
|
127
186
|
@classmethod
|
128
187
|
def zero(cls) -> FlexFloat:
|
129
|
-
"""
|
188
|
+
"""Creates a FlexFloat instance representing zero.
|
130
189
|
|
131
190
|
Returns:
|
132
191
|
FlexFloat: A new FlexFloat instance representing zero.
|
133
192
|
"""
|
134
|
-
exponent =
|
135
|
-
fraction =
|
193
|
+
exponent = cls._bitarray_implementation.zeros(11)
|
194
|
+
fraction = cls._bitarray_implementation.zeros(52)
|
136
195
|
return cls(sign=False, exponent=exponent, fraction=fraction)
|
137
196
|
|
138
197
|
def _is_special_exponent(self) -> bool:
|
139
|
-
"""
|
198
|
+
"""Checks if the exponent represents a special value (NaN or Infinity).
|
140
199
|
|
141
200
|
Returns:
|
142
201
|
bool: True if the exponent is at its maximum value, False otherwise.
|
143
202
|
"""
|
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
203
|
max_signed_value = (1 << (len(self.exponent) - 1)) - 1
|
149
204
|
return self.exponent.to_signed_int() == max_signed_value
|
150
205
|
|
151
206
|
def is_nan(self) -> bool:
|
152
|
-
"""
|
207
|
+
"""Checks if the FlexFloat instance represents NaN (Not a Number).
|
153
208
|
|
154
209
|
Returns:
|
155
210
|
bool: True if the FlexFloat instance is NaN, False otherwise.
|
@@ -157,7 +212,7 @@ class FlexFloat:
|
|
157
212
|
return self._is_special_exponent() and any(self.fraction)
|
158
213
|
|
159
214
|
def is_infinity(self) -> bool:
|
160
|
-
"""
|
215
|
+
"""Checks if the FlexFloat instance represents Infinity.
|
161
216
|
|
162
217
|
Returns:
|
163
218
|
bool: True if the FlexFloat instance is Infinity, False otherwise.
|
@@ -165,7 +220,7 @@ class FlexFloat:
|
|
165
220
|
return self._is_special_exponent() and not any(self.fraction)
|
166
221
|
|
167
222
|
def is_zero(self) -> bool:
|
168
|
-
"""
|
223
|
+
"""Checks if the FlexFloat instance represents zero.
|
169
224
|
|
170
225
|
Returns:
|
171
226
|
bool: True if the FlexFloat instance is zero, False otherwise.
|
@@ -173,7 +228,7 @@ class FlexFloat:
|
|
173
228
|
return not any(self.exponent) and not any(self.fraction)
|
174
229
|
|
175
230
|
def copy(self) -> FlexFloat:
|
176
|
-
"""
|
231
|
+
"""Creates a copy of the FlexFloat instance.
|
177
232
|
|
178
233
|
Returns:
|
179
234
|
FlexFloat: A new FlexFloat instance with the same data as the original.
|
@@ -183,12 +238,13 @@ class FlexFloat:
|
|
183
238
|
)
|
184
239
|
|
185
240
|
def __str__(self) -> str:
|
186
|
-
"""
|
241
|
+
"""Returns a float representation of the FlexFloat using a generic algorithm.
|
187
242
|
|
188
|
-
|
189
|
-
|
243
|
+
Currently, it only operates in one format: scientific notation with 5 decimal
|
244
|
+
places.
|
190
245
|
|
191
|
-
|
246
|
+
Returns:
|
247
|
+
str: The string representation in scientific notation.
|
192
248
|
"""
|
193
249
|
sign_str = "-" if self.sign else ""
|
194
250
|
# Handle special cases first
|
@@ -206,7 +262,7 @@ class FlexFloat:
|
|
206
262
|
# Convert fraction to decimal value between 1 and 2
|
207
263
|
# (starting with 1.0 for the implicit leading bit)
|
208
264
|
mantissa = 1.0
|
209
|
-
for i, bit in enumerate(self.fraction):
|
265
|
+
for i, bit in enumerate(reversed(self.fraction)):
|
210
266
|
if bit:
|
211
267
|
mantissa += 1.0 / (1 << (i + 1))
|
212
268
|
|
@@ -224,6 +280,7 @@ class FlexFloat:
|
|
224
280
|
while normalized_mantissa >= 10.0:
|
225
281
|
normalized_mantissa /= 10.0
|
226
282
|
decimal_exponent += 1
|
283
|
+
|
227
284
|
while normalized_mantissa < 1.0:
|
228
285
|
normalized_mantissa *= 10.0
|
229
286
|
decimal_exponent -= 1
|
@@ -232,7 +289,11 @@ class FlexFloat:
|
|
232
289
|
return f"{sign_str}{normalized_mantissa:.5f}e{decimal_exponent:+03d}"
|
233
290
|
|
234
291
|
def __neg__(self) -> FlexFloat:
|
235
|
-
"""
|
292
|
+
"""Negates the FlexFloat instance.
|
293
|
+
|
294
|
+
Returns:
|
295
|
+
FlexFloat: A new FlexFloat instance with the sign flipped.
|
296
|
+
"""
|
236
297
|
return FlexFloat(
|
237
298
|
sign=not self.sign,
|
238
299
|
exponent=self.exponent.copy(),
|
@@ -241,11 +302,12 @@ class FlexFloat:
|
|
241
302
|
|
242
303
|
@staticmethod
|
243
304
|
def _grow_exponent(exponent: int, exponent_length: int) -> int:
|
244
|
-
"""
|
305
|
+
"""Grows the exponent if it exceeds the maximum value for the current length.
|
245
306
|
|
246
307
|
Args:
|
247
308
|
exponent (int): The current exponent value.
|
248
309
|
exponent_length (int): The current length of the exponent in bits.
|
310
|
+
|
249
311
|
Returns:
|
250
312
|
int: The new exponent length if it needs to be grown, otherwise the same
|
251
313
|
length.
|
@@ -262,12 +324,16 @@ class FlexFloat:
|
|
262
324
|
return exponent_length
|
263
325
|
|
264
326
|
def __add__(self, other: FlexFloat | Number) -> FlexFloat:
|
265
|
-
"""
|
327
|
+
"""Adds two FlexFloat instances together.
|
266
328
|
|
267
329
|
Args:
|
268
|
-
other (FlexFloat |
|
330
|
+
other (FlexFloat | Number): The other FlexFloat instance to add.
|
331
|
+
|
269
332
|
Returns:
|
270
333
|
FlexFloat: A new FlexFloat instance representing the sum.
|
334
|
+
|
335
|
+
Raises:
|
336
|
+
TypeError: If other is not a FlexFloat or numeric type.
|
271
337
|
"""
|
272
338
|
if isinstance(other, Number):
|
273
339
|
other = FlexFloat.from_float(other)
|
@@ -309,9 +375,9 @@ class FlexFloat:
|
|
309
375
|
exponent_self = self.exponent.to_signed_int() + 1
|
310
376
|
exponent_other = other.exponent.to_signed_int() + 1
|
311
377
|
|
312
|
-
# Step 2:
|
313
|
-
mantissa_self = [True]
|
314
|
-
mantissa_other = [True]
|
378
|
+
# Step 2: Append the implicit leading 1 to form the mantissa
|
379
|
+
mantissa_self = self.fraction + [True]
|
380
|
+
mantissa_other = other.fraction + [True]
|
315
381
|
|
316
382
|
# Step 3: Compare exponents (self is always larger or equal)
|
317
383
|
if exponent_self < exponent_other:
|
@@ -332,9 +398,10 @@ class FlexFloat:
|
|
332
398
|
f"got {len(mantissa_other)} bits."
|
333
399
|
)
|
334
400
|
|
335
|
-
|
401
|
+
# 1 leading bit + 52 fraction bits
|
402
|
+
mantissa_result = self._bitarray_implementation.zeros(53)
|
336
403
|
carry = False
|
337
|
-
for i in range(
|
404
|
+
for i in range(53):
|
338
405
|
total = mantissa_self[i] + mantissa_other[i] + carry
|
339
406
|
mantissa_result[i] = total % 2 == 1
|
340
407
|
carry = total > 1
|
@@ -352,20 +419,26 @@ class FlexFloat:
|
|
352
419
|
exponent_self - (1 << (exp_result_length - 1)) < 2
|
353
420
|
), "Exponent growth should not exceed 1 bit."
|
354
421
|
|
355
|
-
exponent_result =
|
422
|
+
exponent_result = self._bitarray_implementation.from_signed_int(
|
423
|
+
exponent_self - 1, exp_result_length
|
424
|
+
)
|
356
425
|
return FlexFloat(
|
357
426
|
sign=self.sign,
|
358
427
|
exponent=exponent_result,
|
359
|
-
fraction=mantissa_result[1
|
428
|
+
fraction=mantissa_result[:-1], # Exclude leading bit
|
360
429
|
)
|
361
430
|
|
362
431
|
def __sub__(self, other: FlexFloat | Number) -> FlexFloat:
|
363
|
-
"""
|
432
|
+
"""Subtracts one FlexFloat instance from another.
|
364
433
|
|
365
434
|
Args:
|
366
|
-
other (FlexFloat |
|
435
|
+
other (FlexFloat | Number): The FlexFloat instance to subtract.
|
436
|
+
|
367
437
|
Returns:
|
368
438
|
FlexFloat: A new FlexFloat instance representing the difference.
|
439
|
+
|
440
|
+
Raises:
|
441
|
+
TypeError: If other is not a FlexFloat or numeric type.
|
369
442
|
"""
|
370
443
|
if isinstance(other, Number):
|
371
444
|
other = FlexFloat.from_float(other)
|
@@ -412,9 +485,9 @@ class FlexFloat:
|
|
412
485
|
exponent_self = self.exponent.to_signed_int() + 1
|
413
486
|
exponent_other = other.exponent.to_signed_int() + 1
|
414
487
|
|
415
|
-
# Step 2:
|
416
|
-
mantissa_self = [True]
|
417
|
-
mantissa_other = [True]
|
488
|
+
# Step 2: Append the implicit leading 1 to form the mantissa
|
489
|
+
mantissa_self = self.fraction + [True]
|
490
|
+
mantissa_other = other.fraction + [True]
|
418
491
|
|
419
492
|
# Step 3: Align mantissas by shifting the smaller exponent
|
420
493
|
result_sign = self.sign
|
@@ -450,9 +523,9 @@ class FlexFloat:
|
|
450
523
|
f"got {len(smaller_mantissa)} bits."
|
451
524
|
)
|
452
525
|
|
453
|
-
mantissa_result =
|
526
|
+
mantissa_result = self._bitarray_implementation.zeros(53)
|
454
527
|
borrow = False
|
455
|
-
for i in range(
|
528
|
+
for i in range(53):
|
456
529
|
diff = int(larger_mantissa[i]) - int(smaller_mantissa[i]) - int(borrow)
|
457
530
|
|
458
531
|
mantissa_result[i] = diff % 2 == 1
|
@@ -463,7 +536,8 @@ class FlexFloat:
|
|
463
536
|
# Step 6: Normalize mantissa and adjust exponent if necessary
|
464
537
|
# Find the first 1 bit (leading bit might have been canceled out)
|
465
538
|
leading_zero_count = next(
|
466
|
-
(i for i, bit in enumerate(mantissa_result) if bit),
|
539
|
+
(i for i, bit in enumerate(reversed(mantissa_result)) if bit),
|
540
|
+
len(mantissa_result),
|
467
541
|
)
|
468
542
|
|
469
543
|
# Handle case where result becomes zero or denormalized
|
@@ -478,21 +552,27 @@ class FlexFloat:
|
|
478
552
|
# Step 7: Grow exponent if necessary (handle underflow)
|
479
553
|
exp_result_length = self._grow_exponent(result_exponent, len(self.exponent))
|
480
554
|
|
481
|
-
exp_result =
|
555
|
+
exp_result = self._bitarray_implementation.from_signed_int(
|
556
|
+
result_exponent - 1, exp_result_length
|
557
|
+
)
|
482
558
|
|
483
559
|
return FlexFloat(
|
484
560
|
sign=result_sign,
|
485
561
|
exponent=exp_result,
|
486
|
-
fraction=mantissa_result[1
|
562
|
+
fraction=mantissa_result[:-1], # Exclude leading bit
|
487
563
|
)
|
488
564
|
|
489
565
|
def __mul__(self, other: FlexFloat | Number) -> FlexFloat:
|
490
|
-
"""
|
566
|
+
"""Multiplies two FlexFloat instances together.
|
491
567
|
|
492
568
|
Args:
|
493
|
-
other (FlexFloat |
|
569
|
+
other (FlexFloat | Number): The other FlexFloat instance to multiply.
|
570
|
+
|
494
571
|
Returns:
|
495
572
|
FlexFloat: A new FlexFloat instance representing the product.
|
573
|
+
|
574
|
+
Raises:
|
575
|
+
TypeError: If other is not a FlexFloat or numeric type.
|
496
576
|
"""
|
497
577
|
if isinstance(other, Number):
|
498
578
|
other = FlexFloat.from_float(other)
|
@@ -536,9 +616,9 @@ class FlexFloat:
|
|
536
616
|
result_exponent = exponent_self + exponent_other
|
537
617
|
|
538
618
|
# Step 4: Multiply mantissas
|
539
|
-
#
|
540
|
-
mantissa_self = [True]
|
541
|
-
mantissa_other = [True]
|
619
|
+
# Append the implicit leading 1 to form the mantissa
|
620
|
+
mantissa_self = self.fraction + [True]
|
621
|
+
mantissa_other = other.fraction + [True]
|
542
622
|
|
543
623
|
# Convert mantissas to integers for multiplication
|
544
624
|
mantissa_self_int = mantissa_self.to_int()
|
@@ -552,8 +632,8 @@ class FlexFloat:
|
|
552
632
|
if product == 0:
|
553
633
|
return FlexFloat.zero()
|
554
634
|
|
555
|
-
product_bits =
|
556
|
-
for i in range(
|
635
|
+
product_bits = self._bitarray_implementation.zeros(106)
|
636
|
+
for i in range(106):
|
557
637
|
product_bits[i] = product & 1 == 1
|
558
638
|
product >>= 1
|
559
639
|
if product <= 0:
|
@@ -561,22 +641,28 @@ class FlexFloat:
|
|
561
641
|
|
562
642
|
# Step 5: Normalize mantissa and adjust exponent if necessary
|
563
643
|
# Find the position of the most significant bit
|
564
|
-
msb_position = next(
|
644
|
+
msb_position = next(
|
645
|
+
(i for i, bit in enumerate(reversed(product_bits)) if bit), None
|
646
|
+
)
|
565
647
|
|
566
648
|
assert msb_position is not None, "Product should not be zero here."
|
567
649
|
|
568
|
-
# The mantissa multiplication gives us a result with 2 integer bits
|
650
|
+
# The mantissa multiplication gives us a result with a 2 integer bits
|
569
651
|
# We need to normalize to have exactly 1 integer bit
|
570
652
|
# If MSB is at position 0, we have a 2-bit integer part (11.xxxxx)
|
571
653
|
# If MSB is at position 1, we have a 1-bit integer part (1.xxxxx)
|
654
|
+
# Mantissa goes from LSB to MSB, so we need to adjust the exponent accordingly
|
572
655
|
if msb_position == 0:
|
573
656
|
result_exponent += 1
|
574
|
-
|
657
|
+
|
658
|
+
# Extract the normalized mantissa
|
659
|
+
lsb_position = 53 - msb_position
|
660
|
+
normalized_mantissa = product_bits[lsb_position : lsb_position + 53]
|
575
661
|
|
576
662
|
# Pad with zeros if we don't have enough bits
|
577
663
|
missing_bits = 53 - len(normalized_mantissa)
|
578
664
|
if missing_bits > 0:
|
579
|
-
normalized_mantissa
|
665
|
+
normalized_mantissa = normalized_mantissa.shift(-missing_bits, fill=False)
|
580
666
|
|
581
667
|
# Step 6: Grow exponent if necessary to accommodate the result
|
582
668
|
exp_result_length = max(len(self.exponent), len(other.exponent))
|
@@ -584,31 +670,38 @@ class FlexFloat:
|
|
584
670
|
# Check if we need to grow the exponent to accommodate the result
|
585
671
|
exp_result_length = self._grow_exponent(result_exponent, exp_result_length)
|
586
672
|
|
587
|
-
exp_result =
|
673
|
+
exp_result = self._bitarray_implementation.from_signed_int(
|
674
|
+
result_exponent - 1, exp_result_length
|
675
|
+
)
|
588
676
|
|
589
677
|
return FlexFloat(
|
590
678
|
sign=result_sign,
|
591
679
|
exponent=exp_result,
|
592
|
-
fraction=normalized_mantissa[1
|
680
|
+
fraction=normalized_mantissa[:-1], # Exclude leading bit
|
593
681
|
)
|
594
682
|
|
595
683
|
def __rmul__(self, other: Number) -> FlexFloat:
|
596
684
|
"""Right-hand multiplication for Number types.
|
597
685
|
|
598
686
|
Args:
|
599
|
-
other (
|
687
|
+
other (Number): The number to multiply with this FlexFloat.
|
688
|
+
|
600
689
|
Returns:
|
601
690
|
FlexFloat: A new FlexFloat instance representing the product.
|
602
691
|
"""
|
603
|
-
return self * other
|
692
|
+
return self * FlexFloat.from_float(other)
|
604
693
|
|
605
694
|
def __truediv__(self, other: FlexFloat | Number) -> FlexFloat:
|
606
|
-
"""
|
695
|
+
"""Divides this FlexFloat by another FlexFloat or number.
|
607
696
|
|
608
697
|
Args:
|
609
|
-
other (FlexFloat |
|
698
|
+
other (FlexFloat | Number): The divisor.
|
699
|
+
|
610
700
|
Returns:
|
611
701
|
FlexFloat: A new FlexFloat instance representing the quotient.
|
702
|
+
|
703
|
+
Raises:
|
704
|
+
TypeError: If other is not a FlexFloat or numeric type.
|
612
705
|
"""
|
613
706
|
if isinstance(other, Number):
|
614
707
|
other = FlexFloat.from_float(other)
|
@@ -661,9 +754,9 @@ class FlexFloat:
|
|
661
754
|
result_exponent = exponent_self - exponent_other
|
662
755
|
|
663
756
|
# Step 4: Divide mantissas
|
664
|
-
#
|
665
|
-
mantissa_self = [True]
|
666
|
-
mantissa_other = [True]
|
757
|
+
# Append the implicit leading 1 to form the mantissa
|
758
|
+
mantissa_self = self.fraction + [True]
|
759
|
+
mantissa_other = other.fraction + [True]
|
667
760
|
|
668
761
|
# Convert mantissas to integers for division
|
669
762
|
mantissa_self_int = mantissa_self.to_int()
|
@@ -682,26 +775,32 @@ class FlexFloat:
|
|
682
775
|
return FlexFloat.zero()
|
683
776
|
|
684
777
|
# Convert quotient to BitArray for easier bit manipulation
|
685
|
-
|
778
|
+
# Use a fixed size for consistency
|
779
|
+
quotient_bitarray = self._bitarray_implementation.zeros(64)
|
686
780
|
temp_quotient = quotient
|
687
|
-
bit_pos =
|
688
|
-
while temp_quotient > 0 and bit_pos
|
781
|
+
bit_pos = 0
|
782
|
+
while temp_quotient > 0 and bit_pos < 64:
|
689
783
|
quotient_bitarray[bit_pos] = (temp_quotient & 1) == 1
|
690
784
|
temp_quotient >>= 1
|
691
|
-
bit_pos
|
785
|
+
bit_pos += 1
|
692
786
|
|
693
787
|
# Step 5: Normalize mantissa and adjust exponent if necessary
|
694
788
|
# Find the position of the most significant bit (first 1)
|
695
|
-
msb_pos = next(
|
789
|
+
msb_pos = next(
|
790
|
+
(i for i, bit in enumerate(reversed(quotient_bitarray)) if bit), None
|
791
|
+
)
|
696
792
|
|
697
793
|
if msb_pos is None:
|
698
794
|
return FlexFloat.zero()
|
699
795
|
|
700
796
|
# Extract exactly 53 bits starting from the MSB (1 integer + 52 fraction)
|
701
|
-
|
702
|
-
normalized_mantissa =
|
703
|
-
|
704
|
-
|
797
|
+
lsb_pos = 11 - msb_pos
|
798
|
+
normalized_mantissa = quotient_bitarray[lsb_pos : lsb_pos + 53]
|
799
|
+
|
800
|
+
# If we don't have enough bits, pad with zeros
|
801
|
+
missing_bits = 53 - len(normalized_mantissa)
|
802
|
+
if missing_bits > 0:
|
803
|
+
normalized_mantissa = normalized_mantissa.shift(-missing_bits, fill=False)
|
705
804
|
|
706
805
|
# Step 6: Grow exponent if necessary to accommodate the result
|
707
806
|
exp_result_length = max(len(self.exponent), len(other.exponent))
|
@@ -709,20 +808,309 @@ class FlexFloat:
|
|
709
808
|
# Check if we need to grow the exponent to accommodate the result
|
710
809
|
exp_result_length = self._grow_exponent(result_exponent, exp_result_length)
|
711
810
|
|
712
|
-
exp_result =
|
811
|
+
exp_result = self._bitarray_implementation.from_signed_int(
|
812
|
+
result_exponent - 1, exp_result_length
|
813
|
+
)
|
713
814
|
|
714
815
|
return FlexFloat(
|
715
816
|
sign=result_sign,
|
716
817
|
exponent=exp_result,
|
717
|
-
fraction=normalized_mantissa[1
|
818
|
+
fraction=normalized_mantissa[:-1], # Exclude leading bit
|
718
819
|
)
|
719
820
|
|
720
821
|
def __rtruediv__(self, other: Number) -> FlexFloat:
|
721
822
|
"""Right-hand division for Number types.
|
722
823
|
|
723
824
|
Args:
|
724
|
-
other (
|
825
|
+
other (Number): The number to divide by this FlexFloat.
|
826
|
+
|
725
827
|
Returns:
|
726
828
|
FlexFloat: A new FlexFloat instance representing the quotient.
|
727
829
|
"""
|
728
830
|
return FlexFloat.from_float(other) / self
|
831
|
+
|
832
|
+
def __abs__(self) -> FlexFloat:
|
833
|
+
"""Returns the absolute value of the FlexFloat instance.
|
834
|
+
|
835
|
+
Returns:
|
836
|
+
FlexFloat: A new FlexFloat instance with the same exponent and fraction, but
|
837
|
+
with the sign set to False (positive).
|
838
|
+
"""
|
839
|
+
return FlexFloat(
|
840
|
+
sign=False,
|
841
|
+
exponent=self.exponent.copy(),
|
842
|
+
fraction=self.fraction.copy(),
|
843
|
+
)
|
844
|
+
|
845
|
+
def abs(self) -> FlexFloat:
|
846
|
+
"""Calculates the absolute value of the FlexFloat instance.
|
847
|
+
|
848
|
+
Returns:
|
849
|
+
FlexFloat: A new FlexFloat instance with the same exponent and fraction, but
|
850
|
+
with the sign set to False (positive).
|
851
|
+
"""
|
852
|
+
return abs(self)
|
853
|
+
|
854
|
+
def exp(self) -> FlexFloat:
|
855
|
+
"""Calculates the exponential of the FlexFloat instance.
|
856
|
+
|
857
|
+
Returns:
|
858
|
+
FlexFloat: A new FlexFloat instance representing e raised to the power of
|
859
|
+
this instance.
|
860
|
+
"""
|
861
|
+
# Use Python's math.exp for the base calculation
|
862
|
+
return FlexFloat.e**self
|
863
|
+
|
864
|
+
def __pow__(self, other: FlexFloat | Number) -> FlexFloat:
|
865
|
+
"""Raises this FlexFloat to the power of another FlexFloat or number.
|
866
|
+
|
867
|
+
Args:
|
868
|
+
other (FlexFloat | Number): The exponent.
|
869
|
+
|
870
|
+
Returns:
|
871
|
+
FlexFloat: A new FlexFloat instance representing the power.
|
872
|
+
|
873
|
+
Raises:
|
874
|
+
TypeError: If other is not a FlexFloat or numeric type.
|
875
|
+
"""
|
876
|
+
if isinstance(other, Number):
|
877
|
+
other = FlexFloat.from_float(other)
|
878
|
+
if not isinstance(other, FlexFloat):
|
879
|
+
raise TypeError("Can only raise FlexFloat instances to a power.")
|
880
|
+
|
881
|
+
# OBJECTIVE: Compute self^other using the identity: a^b = exp(b * ln(a))
|
882
|
+
# For most cases, we use logarithmic computation for generality
|
883
|
+
# Handle special cases first for performance and correctness
|
884
|
+
#
|
885
|
+
# Steps:
|
886
|
+
# 0. Handle special cases (NaN, infinity, zero, one)
|
887
|
+
# 1. Handle negative base with fractional exponent (returns NaN)
|
888
|
+
# 2. For general case: compute ln(|base|) * exponent
|
889
|
+
# 3. Compute exp(result) to get the final power
|
890
|
+
# 4. Handle sign for negative base with integer exponent
|
891
|
+
# 5. Return new FlexFloat instance
|
892
|
+
|
893
|
+
# Step 0: Handle special cases
|
894
|
+
if self.is_nan() or other.is_nan():
|
895
|
+
return FlexFloat.nan()
|
896
|
+
|
897
|
+
# Zero base cases
|
898
|
+
if self.is_zero():
|
899
|
+
if other.is_zero():
|
900
|
+
return FlexFloat.from_float(1.0) # 0^0 = 1 (by convention)
|
901
|
+
if other.sign: # negative exponent
|
902
|
+
return FlexFloat.infinity(sign=False) # 0^(-n) = +infinity
|
903
|
+
return FlexFloat.zero() # 0^n = 0 for positive n
|
904
|
+
|
905
|
+
# One base: 1^anything = 1
|
906
|
+
if (
|
907
|
+
not self.sign
|
908
|
+
and self.exponent.to_signed_int() == -1
|
909
|
+
and not any(self.fraction)
|
910
|
+
):
|
911
|
+
# Check if self is exactly 1.0 (exponent = -1, fraction = 0)
|
912
|
+
return FlexFloat.from_float(1.0)
|
913
|
+
|
914
|
+
# Zero exponent: anything^0 = 1 (except 0^0 handled above)
|
915
|
+
if other.is_zero():
|
916
|
+
return FlexFloat.from_float(1.0)
|
917
|
+
|
918
|
+
# One exponent: anything^1 = anything
|
919
|
+
if (
|
920
|
+
not other.sign
|
921
|
+
and other.exponent.to_signed_int() == -1
|
922
|
+
and not any(other.fraction)
|
923
|
+
):
|
924
|
+
# Check if other is exactly 1.0 (exponent = -1, fraction = 0)
|
925
|
+
return self.copy()
|
926
|
+
|
927
|
+
# Infinity cases
|
928
|
+
if self.is_infinity():
|
929
|
+
if other.is_zero():
|
930
|
+
return FlexFloat.from_float(1.0) # inf^0 = 1
|
931
|
+
if other.sign: # negative exponent
|
932
|
+
return FlexFloat.zero() # inf^(-n) = 0
|
933
|
+
# inf^n = inf, but need to handle sign for negative infinity
|
934
|
+
if not self.sign: # positive infinity
|
935
|
+
return FlexFloat.infinity(sign=False) # (+inf)^n = +inf
|
936
|
+
|
937
|
+
# Check if exponent is an integer for sign determination
|
938
|
+
try:
|
939
|
+
exp_float = other.to_float()
|
940
|
+
if exp_float == int(exp_float): # integer exponent
|
941
|
+
result_sign = int(exp_float) % 2 == 1 # odd = negative
|
942
|
+
else:
|
943
|
+
return FlexFloat.nan() # (-inf)^(non-integer) = NaN
|
944
|
+
except (ValueError, OverflowError):
|
945
|
+
# For very large exponents, assume positive result
|
946
|
+
result_sign = False
|
947
|
+
return FlexFloat.infinity(sign=result_sign)
|
948
|
+
|
949
|
+
if other.is_infinity():
|
950
|
+
try:
|
951
|
+
base_abs = abs(self.to_float())
|
952
|
+
if base_abs == 1.0:
|
953
|
+
return FlexFloat.from_float(1.0) # 1^inf = 1
|
954
|
+
if base_abs > 1.0:
|
955
|
+
return (
|
956
|
+
FlexFloat.infinity(sign=False)
|
957
|
+
if not other.sign
|
958
|
+
else FlexFloat.zero()
|
959
|
+
)
|
960
|
+
# base_abs < 1.0
|
961
|
+
return (
|
962
|
+
FlexFloat.zero()
|
963
|
+
if not other.sign
|
964
|
+
else FlexFloat.infinity(sign=False)
|
965
|
+
)
|
966
|
+
except (ValueError, OverflowError):
|
967
|
+
# For very large/small bases, use log-based approach
|
968
|
+
pass
|
969
|
+
|
970
|
+
# Step 1: Handle negative base with fractional exponent
|
971
|
+
if self.sign:
|
972
|
+
try:
|
973
|
+
# Check if exponent is an integer
|
974
|
+
exp_float = other.to_float()
|
975
|
+
if exp_float != int(exp_float):
|
976
|
+
return FlexFloat.nan() # (-base)^(non-integer) = NaN
|
977
|
+
is_odd_exponent = int(exp_float) % 2 == 1
|
978
|
+
except (ValueError, OverflowError):
|
979
|
+
# For very large exponents, we can't easily determine if integer
|
980
|
+
# Conservative approach: return NaN for negative base
|
981
|
+
return FlexFloat.nan()
|
982
|
+
else:
|
983
|
+
is_odd_exponent = False
|
984
|
+
|
985
|
+
# Step 2-3: General case using a^b = exp(b * ln(a))
|
986
|
+
# We need to compute ln(|self|) * other, then exp of that result
|
987
|
+
|
988
|
+
# For the logarithmic approach, we work with the absolute value
|
989
|
+
abs_base = self.abs()
|
990
|
+
|
991
|
+
# Use the mathematical identity: a^b = exp(b * ln(a))
|
992
|
+
# However, since we don't have ln implemented, we'll use Python's math
|
993
|
+
# for the core calculation and then convert back to FlexFloat
|
994
|
+
try:
|
995
|
+
# Convert to float for the mathematical computation
|
996
|
+
base_float = abs_base.to_float()
|
997
|
+
exp_float = other.to_float()
|
998
|
+
|
999
|
+
# For very small results that would underflow in float arithmetic,
|
1000
|
+
# we need to use extended precision
|
1001
|
+
log_result_estimate = exp_float * math.log(base_float)
|
1002
|
+
|
1003
|
+
# Check if the result would be too small for standard float
|
1004
|
+
if log_result_estimate < -700: # This would underflow to 0 in float
|
1005
|
+
# Use extended precision approach
|
1006
|
+
# Estimate the required exponent bits for the very small result
|
1007
|
+
estimated_exp = int(log_result_estimate / LOG10_2)
|
1008
|
+
required_exp_bits = max(11, abs(estimated_exp).bit_length() + 2)
|
1009
|
+
|
1010
|
+
# Create a small result with extended exponent
|
1011
|
+
small_exp = self._bitarray_implementation.from_signed_int(
|
1012
|
+
estimated_exp, required_exp_bits
|
1013
|
+
)
|
1014
|
+
# Use a normalized mantissa (leading 1 + some fraction bits)
|
1015
|
+
result = FlexFloat(
|
1016
|
+
sign=False,
|
1017
|
+
exponent=small_exp,
|
1018
|
+
fraction=self._bitarray_implementation.ones(52),
|
1019
|
+
)
|
1020
|
+
else:
|
1021
|
+
# Compute the power using Python's built-in pow
|
1022
|
+
result_float = pow(base_float, exp_float)
|
1023
|
+
|
1024
|
+
# Handle potential overflow/underflow
|
1025
|
+
if math.isinf(result_float):
|
1026
|
+
return FlexFloat.infinity(sign=False)
|
1027
|
+
if result_float == 0.0:
|
1028
|
+
# Even if Python returns 0, we might want extended precision
|
1029
|
+
if log_result_estimate < -300: # Very small but not quite underflow
|
1030
|
+
estimated_exp = int(log_result_estimate / LOG10_2)
|
1031
|
+
required_exp_bits = max(11, abs(estimated_exp).bit_length() + 2)
|
1032
|
+
small_exp = self._bitarray_implementation.from_signed_int(
|
1033
|
+
estimated_exp, required_exp_bits
|
1034
|
+
)
|
1035
|
+
result = FlexFloat(
|
1036
|
+
sign=False,
|
1037
|
+
exponent=small_exp,
|
1038
|
+
fraction=self._bitarray_implementation.ones(52),
|
1039
|
+
)
|
1040
|
+
else:
|
1041
|
+
return FlexFloat.zero()
|
1042
|
+
if math.isnan(result_float):
|
1043
|
+
return FlexFloat.nan()
|
1044
|
+
|
1045
|
+
# Convert result back to FlexFloat
|
1046
|
+
result = FlexFloat.from_float(result_float)
|
1047
|
+
|
1048
|
+
except (ValueError, OverflowError):
|
1049
|
+
# If float computation overflows, use extended precision approach
|
1050
|
+
# This is a fallback for when the result is too large for float
|
1051
|
+
|
1052
|
+
# For very large results, we estimate the exponent growth needed
|
1053
|
+
try:
|
1054
|
+
# Estimate log(result) = exponent * log(base)
|
1055
|
+
log_base = (
|
1056
|
+
math.log(abs(self.to_float()))
|
1057
|
+
if not self.is_zero()
|
1058
|
+
else -float("inf")
|
1059
|
+
)
|
1060
|
+
exp_float = other.to_float()
|
1061
|
+
estimated_log_result = exp_float * log_base
|
1062
|
+
|
1063
|
+
# Estimate the required exponent bits
|
1064
|
+
estimated_exp_float = estimated_log_result / LOG10_2
|
1065
|
+
required_exp_bits = max(
|
1066
|
+
11, int(abs(estimated_exp_float)).bit_length() + 2
|
1067
|
+
)
|
1068
|
+
|
1069
|
+
# Create a result with extended exponent
|
1070
|
+
# This is a simplified approach - a full implementation would
|
1071
|
+
# use arbitrary precision arithmetic
|
1072
|
+
if estimated_exp_float > 0:
|
1073
|
+
# Very large positive result
|
1074
|
+
large_exp = self._bitarray_implementation.from_signed_int(
|
1075
|
+
int(estimated_exp_float), required_exp_bits
|
1076
|
+
)
|
1077
|
+
result = FlexFloat(
|
1078
|
+
sign=False,
|
1079
|
+
exponent=large_exp,
|
1080
|
+
fraction=self._bitarray_implementation.ones(52),
|
1081
|
+
)
|
1082
|
+
else:
|
1083
|
+
# Very small positive result
|
1084
|
+
small_exp = self._bitarray_implementation.from_signed_int(
|
1085
|
+
int(estimated_exp_float), required_exp_bits
|
1086
|
+
)
|
1087
|
+
result = FlexFloat(
|
1088
|
+
sign=False,
|
1089
|
+
exponent=small_exp,
|
1090
|
+
fraction=self._bitarray_implementation.ones(52),
|
1091
|
+
)
|
1092
|
+
|
1093
|
+
except (ValueError, OverflowError, ZeroDivisionError):
|
1094
|
+
# Ultimate fallback
|
1095
|
+
return FlexFloat.nan()
|
1096
|
+
|
1097
|
+
# Step 4: Handle sign for negative base with integer exponent
|
1098
|
+
if self.sign and is_odd_exponent:
|
1099
|
+
result.sign = True
|
1100
|
+
|
1101
|
+
return result
|
1102
|
+
|
1103
|
+
def __rpow__(self, base: Number) -> FlexFloat:
|
1104
|
+
"""Right-hand power operation for Number types.
|
1105
|
+
|
1106
|
+
Args:
|
1107
|
+
base (Number): The base to raise to the power of this FlexFloat.
|
1108
|
+
|
1109
|
+
Returns:
|
1110
|
+
FlexFloat: A new FlexFloat instance representing the power.
|
1111
|
+
"""
|
1112
|
+
return FlexFloat.from_float(base) ** self
|
1113
|
+
|
1114
|
+
|
1115
|
+
# Initialize class variable after class definition
|
1116
|
+
FlexFloat.e = FlexFloat.from_float(math.e)
|