flexfloat 0.1.0__py3-none-any.whl → 0.1.1__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 CHANGED
@@ -7,7 +7,7 @@ with growable exponents and fixed-size fractions.
7
7
  from .bitarray import BitArray
8
8
  from .core import FlexFloat
9
9
 
10
- __version__ = "0.1.0"
10
+ __version__ = "0.1.1"
11
11
  __author__ = "Ferran Sanchez Llado"
12
12
 
13
13
  __all__ = ["FlexFloat", "BitArray"]
flexfloat/bitarray.py CHANGED
@@ -9,8 +9,8 @@ from typing import Iterator, overload
9
9
  class BitArray:
10
10
  """A bit array class that encapsulates a list of booleans with utility methods.
11
11
 
12
- This class provides all the functionality previously available through utility functions,
13
- now encapsulated as methods for better object-oriented design.
12
+ This class provides all the functionality previously available through utility
13
+ functions, now encapsulated as methods for better object-oriented design.
14
14
  """
15
15
 
16
16
  def __init__(self, bits: list[bool] | None = None):
@@ -119,7 +119,8 @@ class BitArray:
119
119
  return sum((1 << i) for i, bit in enumerate(reversed(self._bits)) if bit)
120
120
 
121
121
  def to_signed_int(self) -> int:
122
- """Convert a bit array into a signed integer using off-set binary representation.
122
+ """Convert a bit array into a signed integer using off-set binary
123
+ representation.
123
124
 
124
125
  Returns:
125
126
  int: The signed integer represented by the bit array.
@@ -137,13 +138,16 @@ class BitArray:
137
138
  def shift(self, shift_amount: int, fill: bool = False) -> BitArray:
138
139
  """Shift the bit array left or right by a specified number of bits.
139
140
 
140
- This function shifts the bits in the array, filling in new bits with the specified fill value.
141
- If the shift is positive, it shifts left and fills with the fill value at the end.
142
- If the shift is negative, it shifts right and fills with the fill value at the start.
141
+ This function shifts the bits in the array, filling in new bits with the
142
+ specified fill value.
143
+ If the value is positive, it shifts left; if negative, it shifts right.
144
+ Fills the new bits with the specified fill value (default is False).
143
145
 
144
146
  Args:
145
- shift_amount (int): The number of bits to shift. Positive for left shift, negative for right shift.
146
- fill (bool): The value to fill in the new bits created by the shift. Defaults to False.
147
+ shift_amount (int): The number of bits to shift. Positive for left shift,
148
+ negative for right shift.
149
+ fill (bool): The value to fill in the new bits created by the shift.
150
+ Defaults to False.
147
151
  Returns:
148
152
  BitArray: A new BitArray with the bits shifted and filled.
149
153
  """
flexfloat/core.py CHANGED
@@ -2,22 +2,30 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import math
6
+ from typing import Final
7
+
5
8
  from .bitarray import BitArray
6
9
  from .types import Number
7
10
 
11
+ LOG10_2: Final[float] = math.log10(2)
12
+
8
13
 
9
14
  class FlexFloat:
10
- """A class to represent a floating-point number with growable exponent and fixed-size fraction.
11
- This class is designed to handle very large or very small numbers by adjusting the exponent dynamically.
12
- While keeping the mantissa (fraction) fixed in size.
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.
13
19
 
14
20
  This class follows the IEEE 754 double-precision floating-point format,
15
21
  but extends it to allow for a growable exponent and a fixed-size fraction.
16
22
 
17
23
  Attributes:
18
24
  sign (bool): The sign of the number (True for negative, False for positive).
19
- exponent (BitArray): A growable bit array representing the exponent (uses off-set binary representation).
20
- fraction (BitArray): A fixed-size bit array representing the fraction (mantissa) of the number.
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.
21
29
  """
22
30
 
23
31
  def __init__(
@@ -30,8 +38,8 @@ class FlexFloat:
30
38
 
31
39
  Args:
32
40
  sign (bool): The sign of the number (True for negative, False for positive).
33
- exponent (BitArray | None): The exponent bit array. If None, defaults to a zero exponent.
34
- fraction (BitArray | None): The fraction bit array. If None, defaults to a zero fraction.
41
+ exponent (BitArray | None): The exponent bit array (If None, represents 0).
42
+ fraction (BitArray | None): The fraction bit array (If None, represents 0).
35
43
  """
36
44
  self.sign = sign
37
45
  self.exponent = exponent if exponent is not None else BitArray.zeros(11)
@@ -54,7 +62,7 @@ class FlexFloat:
54
62
  def to_float(self) -> float:
55
63
  """Convert the FlexFloat instance back to a 64-bit float.
56
64
 
57
- If float is bigger than 64 bits, it will truncate the value to fit into a 64-bit float.
65
+ If float is bigger than 64 bits, it will truncate the value to fit.
58
66
 
59
67
  Returns:
60
68
  float: The floating-point number represented by the FlexFloat instance.
@@ -73,7 +81,12 @@ class FlexFloat:
73
81
  Returns:
74
82
  str: A string representation of the FlexFloat instance.
75
83
  """
76
- return f"FlexFloat(sign={self.sign}, exponent={self.exponent}, fraction={self.fraction})"
84
+ return (
85
+ "FlexFloat("
86
+ f"sign={self.sign}, "
87
+ f"exponent={self.exponent}, "
88
+ f"fraction={self.fraction})"
89
+ )
77
90
 
78
91
  def pretty(self) -> str:
79
92
  """Return an easier to read string representation of the FlexFloat instance.
@@ -103,7 +116,7 @@ class FlexFloat:
103
116
  """Create a FlexFloat instance representing Infinity.
104
117
 
105
118
  Args:
106
- sign (bool): The sign of the infinity (True for negative, False for positive).
119
+ sign (bool): Indicates if the infinity is negative.
107
120
  Returns:
108
121
  FlexFloat: A new FlexFloat instance representing Infinity.
109
122
  """
@@ -130,7 +143,8 @@ class FlexFloat:
130
143
  """
131
144
  # In IEEE 754, special values have all exponent bits set to 1
132
145
  # This corresponds to the maximum value in the unsigned representation
133
- # For signed offset binary, the maximum value is 2^(n-1) - 1 where n is the number of bits
146
+ # For signed offset binary, the maximum value is 2^(n-1) - 1
147
+ # where n is the number of bits
134
148
  max_signed_value = (1 << (len(self.exponent) - 1)) - 1
135
149
  return self.exponent.to_signed_int() == max_signed_value
136
150
 
@@ -162,36 +176,60 @@ class FlexFloat:
162
176
  """Create a copy of the FlexFloat instance.
163
177
 
164
178
  Returns:
165
- FlexFloat: A new FlexFloat instance with the same sign, exponent, and fraction.
179
+ FlexFloat: A new FlexFloat instance with the same data as the original.
166
180
  """
167
181
  return FlexFloat(
168
182
  sign=self.sign, exponent=self.exponent.copy(), fraction=self.fraction.copy()
169
183
  )
170
184
 
171
185
  def __str__(self) -> str:
172
- """Float representation of the FlexFloat."""
173
- sign = "-" if self.sign else ""
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"
174
200
 
175
- exponent_value = self.exponent.to_signed_int()
176
- if exponent_value == 0:
177
- return f"{sign}0.0"
178
- max_exponent = 2 ** len(self.exponent) - 1
179
- # Check NaN or Infinity
180
- if exponent_value == max_exponent:
181
- if any(self.fraction):
182
- return f"{sign}NaN"
183
- return f"{sign}Infinity"
184
-
185
- fraction_value: float = 1
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
186
209
  for i, bit in enumerate(self.fraction):
187
210
  if bit:
188
- fraction_value += 2 ** -(i + 1)
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)
189
222
 
190
- if exponent_value == 0:
191
- return f"{sign}{fraction_value}.0"
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
192
230
 
193
- # raise NotImplementedError("String representation for non-zero exponent not implemented yet.")
194
- return ""
231
+ # Format with 5 decimal places
232
+ return f"{sign_str}{normalized_mantissa:.5f}e{decimal_exponent:+03d}"
195
233
 
196
234
  def __neg__(self) -> FlexFloat:
197
235
  """Negate the FlexFloat instance."""
@@ -209,7 +247,8 @@ class FlexFloat:
209
247
  exponent (int): The current exponent value.
210
248
  exponent_length (int): The current length of the exponent in bits.
211
249
  Returns:
212
- int: The new exponent length if it needs to be grown, otherwise the same length.
250
+ int: The new exponent length if it needs to be grown, otherwise the same
251
+ length.
213
252
  """
214
253
  while True:
215
254
  half = 1 << (exponent_length - 1)
@@ -239,7 +278,7 @@ class FlexFloat:
239
278
  return self - (-other)
240
279
 
241
280
  # OBJECTIVE: Add two FlexFloat instances together.
242
- # Based on: https://www.sciencedirect.com/topics/computer-science/floating-point-addition
281
+ # https://www.sciencedirect.com/topics/computer-science/floating-point-addition
243
282
  # and: https://cse.hkust.edu.hk/~cktang/cs180/notes/lec21.pdf
244
283
  #
245
284
  # Steps:
@@ -285,8 +324,13 @@ class FlexFloat:
285
324
  mantissa_other = mantissa_other.shift(shift_amount)
286
325
 
287
326
  # Step 5: Add mantissas
288
- assert len(mantissa_self) == 53, "Fraction must be 53 bits long. (1 leading bit + 52 fraction bits)" # fmt: skip
289
- assert len(mantissa_self) == len(mantissa_other), f"Mantissas must be the same length. Expected 53 bits, got {len(mantissa_other)} bits." # fmt: skip
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
+ )
290
334
 
291
335
  mantissa_result = BitArray.zeros(53) # 1 leading bit + 52 fraction bits
292
336
  carry = False
@@ -398,8 +442,13 @@ class FlexFloat:
398
442
  result_sign = not self.sign
399
443
 
400
444
  # Step 5: Subtract mantissas (larger - smaller)
401
- assert len(larger_mantissa) == 53, "Mantissa must be 53 bits long. (1 leading bit + 52 fraction bits)" # fmt: skip
402
- assert len(larger_mantissa) == len(smaller_mantissa), f"Mantissas must be the same length. Expected 53 bits, got {len(smaller_mantissa)} bits." # fmt: skip
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
+ )
403
452
 
404
453
  mantissa_result = BitArray.zeros(53)
405
454
  borrow = False
@@ -451,7 +500,7 @@ class FlexFloat:
451
500
  raise TypeError("Can only multiply FlexFloat instances.")
452
501
 
453
502
  # OBJECTIVE: Multiply two FlexFloat instances together.
454
- # Based on: https://www.rfwireless-world.com/tutorials/ieee-754-floating-point-arithmetic
503
+ # https://www.rfwireless-world.com/tutorials/ieee-754-floating-point-arithmetic
455
504
  #
456
505
  # Steps:
457
506
  # 0. Handle special cases (NaN, Infinity, zero).
@@ -567,7 +616,7 @@ class FlexFloat:
567
616
  raise TypeError("Can only divide FlexFloat instances.")
568
617
 
569
618
  # OBJECTIVE: Divide two FlexFloat instances.
570
- # Based on: https://www.rfwireless-world.com/tutorials/ieee-754-floating-point-arithmetic
619
+ # https://www.rfwireless-world.com/tutorials/ieee-754-floating-point-arithmetic
571
620
  #
572
621
  # Steps:
573
622
  # 0. Handle special cases (NaN, Infinity, zero).
@@ -603,7 +652,8 @@ class FlexFloat:
603
652
  result_sign = self.sign ^ other.sign
604
653
 
605
654
  # Step 2: Extract exponent and fraction bits
606
- # Note: The stored exponent needs +1 to get the actual value (like in multiplication)
655
+ # Note: The stored exponent needs +1 to get the actual value
656
+ # (like in multiplication)
607
657
  exponent_self = self.exponent.to_signed_int() + 1
608
658
  exponent_other = other.exponent.to_signed_int() + 1
609
659
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flexfloat
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: A library for arbitrary precision floating point arithmetic
5
5
  Author: Ferran Sanchez Llado
6
6
  License: MIT
@@ -0,0 +1,9 @@
1
+ flexfloat/__init__.py,sha256=SFtec_6Rr65_Sr83rhoh9VT7Egj8N-e2F0fyMQbBqKg,378
2
+ flexfloat/bitarray.py,sha256=hlJtoe50Lx6KdNTyMNFkJCBON4t_gtCDc3hWfX_bza4,9196
3
+ flexfloat/core.py,sha256=7bnw7aJvnFOD1RTtZvH9nc4QCKcSowqsSGhcncAMlJI,28771
4
+ flexfloat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ flexfloat/types.py,sha256=SFTYmG3BHLiCJc0LvNwOnByRLRcRXH2erINeqLyHLKs,118
6
+ flexfloat-0.1.1.dist-info/METADATA,sha256=SMM9FyWRr08gI32_gssW0bkCLxPF-MTm9lim3J_-Wdk,2459
7
+ flexfloat-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ flexfloat-0.1.1.dist-info/top_level.txt,sha256=82S8dY2UoNZh-9pwg7tUvbwB3uw2s3mfEoyUW6vCMdU,10
9
+ flexfloat-0.1.1.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- flexfloat/__init__.py,sha256=fvimTZm80CHm8bp11bID5twYoISERcatgx21snQxsz8,378
2
- flexfloat/bitarray.py,sha256=Ss0bZUUpfsBPJkPaGymXd2O-4e-UCanUva_encnc4MI,9172
3
- flexfloat/core.py,sha256=KK8LfRlqReZuVgyN-fQwYDsM1TlaJtYjHcafPYSPZeQ,27570
4
- flexfloat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- flexfloat/types.py,sha256=SFTYmG3BHLiCJc0LvNwOnByRLRcRXH2erINeqLyHLKs,118
6
- flexfloat-0.1.0.dist-info/METADATA,sha256=8QUYOP_vFgljckHUm02jixPFfDYkt-XZdW5Wahm8TbQ,2459
7
- flexfloat-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- flexfloat-0.1.0.dist-info/top_level.txt,sha256=82S8dY2UoNZh-9pwg7tUvbwB3uw2s3mfEoyUW6vCMdU,10
9
- flexfloat-0.1.0.dist-info/RECORD,,