flexfloat 0.1.2__py3-none-any.whl → 0.1.3__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
@@ -4,10 +4,29 @@ This package provides the FlexFloat class for handling floating-point numbers
4
4
  with growable exponents and fixed-size fractions.
5
5
  """
6
6
 
7
- from .bitarray import BitArray
7
+ from .bitarray import (
8
+ BitArray,
9
+ BitArrayType,
10
+ Int64BitArray,
11
+ ListBitArray,
12
+ create_bitarray,
13
+ get_available_implementations,
14
+ parse_bitarray,
15
+ set_default_implementation,
16
+ )
8
17
  from .core import FlexFloat
9
18
 
10
- __version__ = "0.1.2"
19
+ __version__ = "0.1.3"
11
20
  __author__ = "Ferran Sanchez Llado"
12
21
 
13
- __all__ = ["FlexFloat", "BitArray"]
22
+ __all__ = [
23
+ "FlexFloat",
24
+ "BitArrayType",
25
+ "BitArray",
26
+ "ListBitArray",
27
+ "Int64BitArray",
28
+ "create_bitarray",
29
+ "set_default_implementation",
30
+ "get_available_implementations",
31
+ "parse_bitarray",
32
+ ]
@@ -0,0 +1,90 @@
1
+ """BitArray implementation for the flexfloat package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Dict, Type
6
+
7
+ from .bitarray import BitArray
8
+ from .bitarray_int64 import Int64BitArray
9
+ from .bitarray_list import ListBitArray
10
+ from .bitarray_mixins import BitArrayCommonMixin
11
+
12
+ # Type alias for the default BitArray implementation
13
+ BitArrayType: Type[BitArray] = ListBitArray
14
+
15
+ # Available implementations
16
+ IMPLEMENTATIONS: Dict[str, Type[BitArray]] = {
17
+ "list": ListBitArray,
18
+ "int64": Int64BitArray,
19
+ }
20
+
21
+
22
+ def create_bitarray(
23
+ implementation: str = "list", bits: list[bool] | None = None
24
+ ) -> BitArray:
25
+ """Factory function to create a BitArray with the specified implementation.
26
+
27
+ Args:
28
+ implementation: The implementation to use ("list" or "int64")
29
+ bits: Initial list of boolean values
30
+
31
+ Returns:
32
+ BitArray: A BitArray instance using the specified implementation
33
+
34
+ Raises:
35
+ ValueError: If the implementation is not supported
36
+ """
37
+ if implementation not in IMPLEMENTATIONS:
38
+ raise ValueError(
39
+ f"Unknown implementation '{implementation}'. "
40
+ f"Available: {list(IMPLEMENTATIONS.keys())}"
41
+ )
42
+
43
+ return IMPLEMENTATIONS[implementation](bits)
44
+
45
+
46
+ def set_default_implementation(implementation: str) -> None:
47
+ """Set the default BitArray implementation.
48
+
49
+ Args:
50
+ implementation: The implementation to use as default ("list" or "int64")
51
+
52
+ Raises:
53
+ ValueError: If the implementation is not supported
54
+ """
55
+ global BitArrayType
56
+
57
+ if implementation not in IMPLEMENTATIONS:
58
+ raise ValueError(
59
+ f"Unknown implementation '{implementation}'. "
60
+ f"Available: {list(IMPLEMENTATIONS.keys())}"
61
+ )
62
+
63
+ BitArrayType = IMPLEMENTATIONS[implementation]
64
+
65
+
66
+ def get_available_implementations() -> list[str]:
67
+ """Get the list of available BitArray implementations.
68
+
69
+ Returns:
70
+ list[str]: List of available implementation names
71
+ """
72
+ return list(IMPLEMENTATIONS.keys())
73
+
74
+
75
+ # Maintain backward compatibility by exposing the methods as module-level functions
76
+ def parse_bitarray(bitstring: str) -> BitArray:
77
+ """Parse a string of bits (with optional spaces) into a BitArray instance."""
78
+ return BitArrayType.parse_bitarray(bitstring)
79
+
80
+
81
+ __all__ = [
82
+ "BitArray",
83
+ "ListBitArray",
84
+ "Int64BitArray",
85
+ "BitArrayCommonMixin",
86
+ "create_bitarray",
87
+ "set_default_implementation",
88
+ "get_available_implementations",
89
+ "parse_bitarray",
90
+ ]
@@ -0,0 +1,198 @@
1
+ """BitArray protocol definition for the flexfloat package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Iterator, Protocol, overload, runtime_checkable
6
+
7
+
8
+ @runtime_checkable
9
+ class BitArray(Protocol):
10
+ """Protocol defining the interface for BitArray implementations.
11
+
12
+ This protocol defines all the methods and properties that a BitArray
13
+ implementation must provide.
14
+ """
15
+
16
+ def __init__(self, bits: list[bool] | None = None) -> None:
17
+ """Initialize a BitArray.
18
+
19
+ Args:
20
+ bits: Initial list of boolean values. Defaults to empty list.
21
+ """
22
+ ...
23
+
24
+ @classmethod
25
+ def from_float(cls, value: float) -> BitArray:
26
+ """Convert a floating-point number to a bit array.
27
+
28
+ Args:
29
+ value (float): The floating-point number to convert.
30
+ Returns:
31
+ BitArrayProtocol: A BitArray representing the bits of the floating-point
32
+ number.
33
+ """
34
+ ...
35
+
36
+ @classmethod
37
+ def from_signed_int(cls, value: int, length: int) -> BitArray:
38
+ """Convert a signed integer to a bit array using off-set binary representation.
39
+
40
+ Args:
41
+ value (int): The signed integer to convert.
42
+ length (int): The length of the resulting bit array.
43
+ Returns:
44
+ BitArrayProtocol: A BitArray representing the bits of the signed integer.
45
+ Raises:
46
+ AssertionError: If the value is out of range for the specified length.
47
+ """
48
+ ...
49
+
50
+ @classmethod
51
+ def zeros(cls, length: int) -> BitArray:
52
+ """Create a BitArray filled with zeros.
53
+
54
+ Args:
55
+ length: The length of the bit array.
56
+ Returns:
57
+ BitArrayProtocol: A BitArray filled with False values.
58
+ """
59
+ ...
60
+
61
+ @classmethod
62
+ def ones(cls, length: int) -> BitArray:
63
+ """Create a BitArray filled with ones.
64
+
65
+ Args:
66
+ length: The length of the bit array.
67
+ Returns:
68
+ BitArrayProtocol: A BitArray filled with True values.
69
+ """
70
+ ...
71
+
72
+ @staticmethod
73
+ def parse_bitarray(bitstring: str) -> BitArray:
74
+ """Parse a string of bits (with optional spaces) into a BitArray instance."""
75
+ ...
76
+
77
+ def to_float(self) -> float:
78
+ """Convert a 64-bit array to a floating-point number.
79
+
80
+ Returns:
81
+ float: The floating-point number represented by the bit array.
82
+ Raises:
83
+ AssertionError: If the bit array is not 64 bits long.
84
+ """
85
+ ...
86
+
87
+ def to_int(self) -> int:
88
+ """Convert the bit array to an unsigned integer.
89
+
90
+ Returns:
91
+ int: The integer represented by the bit array.
92
+ """
93
+ ...
94
+
95
+ def to_signed_int(self) -> int:
96
+ """Convert a bit array into a signed integer using off-set binary
97
+ representation.
98
+
99
+ Returns:
100
+ int: The signed integer represented by the bit array.
101
+ Raises:
102
+ AssertionError: If the bit array is empty.
103
+ """
104
+ ...
105
+
106
+ def shift(self, shift_amount: int, fill: bool = False) -> BitArray:
107
+ """Shift the bit array left or right by a specified number of bits.
108
+
109
+ This function shifts the bits in the array, filling in new bits with the
110
+ specified fill value.
111
+ If the value is positive, it shifts left; if negative, it shifts right.
112
+ Fills the new bits with the specified fill value (default is False).
113
+
114
+ Args:
115
+ shift_amount (int): The number of bits to shift. Positive for left shift,
116
+ negative for right shift.
117
+ fill (bool): The value to fill in the new bits created by the shift.
118
+ Defaults to False.
119
+ Returns:
120
+ BitArrayProtocol: A new BitArray with the bits shifted and filled.
121
+ """
122
+ ...
123
+
124
+ def copy(self) -> BitArray:
125
+ """Create a copy of the bit array.
126
+
127
+ Returns:
128
+ BitArrayProtocol: A new BitArray with the same bits.
129
+ """
130
+ ...
131
+
132
+ def __len__(self) -> int:
133
+ """Return the length of the bit array."""
134
+ ...
135
+
136
+ @overload
137
+ def __getitem__(self, index: int) -> bool: ...
138
+ @overload
139
+ def __getitem__(self, index: slice) -> BitArray: ...
140
+
141
+ def __getitem__(self, index: int | slice) -> bool | BitArray:
142
+ """Get an item or slice from the bit array."""
143
+ ...
144
+
145
+ @overload
146
+ def __setitem__(self, index: int, value: bool) -> None: ...
147
+ @overload
148
+ def __setitem__(self, index: slice, value: BitArray | list[bool]) -> None: ...
149
+
150
+ def __setitem__(
151
+ self, index: int | slice, value: bool | list[bool] | BitArray
152
+ ) -> None:
153
+ """Set an item or slice in the bit array."""
154
+ ...
155
+
156
+ def __iter__(self) -> Iterator[bool]:
157
+ """Iterate over the bits in the array."""
158
+ ...
159
+
160
+ def __add__(self, other: BitArray | list[bool]) -> BitArray:
161
+ """Concatenate two bit arrays."""
162
+ ...
163
+
164
+ def __radd__(self, other: list[bool]) -> BitArray:
165
+ """Reverse concatenation with a list."""
166
+ ...
167
+
168
+ def __eq__(self, other: object) -> bool:
169
+ """Check equality with another BitArray or list."""
170
+ ...
171
+
172
+ def __bool__(self) -> bool:
173
+ """Return True if any bit is set."""
174
+ ...
175
+
176
+ def __repr__(self) -> str:
177
+ """Return a string representation of the BitArray."""
178
+ ...
179
+
180
+ def __str__(self) -> str:
181
+ """Return a string representation of the bits."""
182
+ ...
183
+
184
+ def any(self) -> bool:
185
+ """Return True if any bit is set to True."""
186
+ ...
187
+
188
+ def all(self) -> bool:
189
+ """Return True if all bits are set to True."""
190
+ ...
191
+
192
+ def count(self, value: bool = True) -> int:
193
+ """Count the number of bits set to the specified value."""
194
+ ...
195
+
196
+ def reverse(self) -> BitArray:
197
+ """Return a new BitArray with the bits in reverse order."""
198
+ ...
@@ -0,0 +1,308 @@
1
+ """Memory-efficient int64-based BitArray implementation for the flexfloat package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import struct
6
+ from typing import Iterator, overload
7
+
8
+ from .bitarray import BitArray
9
+ from .bitarray_mixins import BitArrayCommonMixin
10
+
11
+
12
+ class Int64BitArray(BitArrayCommonMixin):
13
+ """A memory-efficient bit array class using a list of int64 values.
14
+
15
+ This implementation packs 64 bits per integer, making it more memory efficient
16
+ for large bit arrays compared to the boolean list implementation.
17
+ """
18
+
19
+ def __init__(self, bits: list[bool] | None = None):
20
+ """Initialize a BitArray.
21
+
22
+ Args:
23
+ bits: Initial list of boolean values. Defaults to empty list.
24
+ """
25
+ if bits is None:
26
+ bits = []
27
+
28
+ self._length = len(bits)
29
+ # Pack bits into int64 chunks (64 bits per int)
30
+ self._chunks: list[int] = []
31
+
32
+ for i in range(0, len(bits), 64):
33
+ chunk = 0
34
+ chunk_end = min(i + 64, len(bits))
35
+ for j in range(i, chunk_end):
36
+ if bits[j]:
37
+ chunk |= 1 << (63 - (j - i))
38
+ self._chunks.append(chunk)
39
+
40
+ @classmethod
41
+ def zeros(cls, length: int) -> Int64BitArray:
42
+ """Create a BitArray filled with zeros.
43
+
44
+ Args:
45
+ length: The length of the bit array.
46
+ Returns:
47
+ Int64BitArray: A BitArray filled with False values.
48
+ """
49
+ instance = cls.__new__(cls)
50
+ instance._length = length
51
+ instance._chunks = [0] * ((length + 63) // 64)
52
+ return instance
53
+
54
+ @classmethod
55
+ def ones(cls, length: int) -> Int64BitArray:
56
+ """Create a BitArray filled with ones.
57
+
58
+ Args:
59
+ length: The length of the bit array.
60
+ Returns:
61
+ Int64BitArray: A BitArray filled with True values.
62
+ """
63
+ instance = cls.__new__(cls)
64
+ instance._length = length
65
+ num_full_chunks = length // 64
66
+ remaining_bits = length % 64
67
+
68
+ instance._chunks = []
69
+
70
+ # Add full chunks of all 1s
71
+ for _ in range(num_full_chunks):
72
+ instance._chunks.append(0xFFFFFFFFFFFFFFFF) # All 64 bits set
73
+
74
+ # Add partial chunk if needed
75
+ if remaining_bits > 0:
76
+ partial_chunk = (1 << remaining_bits) - 1
77
+ partial_chunk <<= 64 - remaining_bits # Left-align the bits
78
+ instance._chunks.append(partial_chunk)
79
+
80
+ return instance
81
+
82
+ @staticmethod
83
+ def parse_bitarray(bitstring: str) -> Int64BitArray:
84
+ """Parse a string of bits (with optional spaces) into a BitArray instance."""
85
+ bits = [c == "1" for c in bitstring if c in "01"]
86
+ return Int64BitArray(bits)
87
+
88
+ def _get_bit(self, index: int) -> bool:
89
+ """Get a single bit at the specified index."""
90
+ if index < 0 or index >= self._length:
91
+ raise IndexError("Bit index out of range")
92
+
93
+ chunk_index = index // 64
94
+ bit_index = index % 64
95
+ bit_position = 63 - bit_index # Left-aligned
96
+
97
+ return bool(self._chunks[chunk_index] & (1 << bit_position))
98
+
99
+ def _set_bit(self, index: int, value: bool) -> None:
100
+ """Set a single bit at the specified index."""
101
+ if index < 0 or index >= self._length:
102
+ raise IndexError("Bit index out of range")
103
+
104
+ chunk_index = index // 64
105
+ bit_index = index % 64
106
+ bit_position = 63 - bit_index # Left-aligned
107
+
108
+ if value:
109
+ self._chunks[chunk_index] |= 1 << bit_position
110
+ else:
111
+ self._chunks[chunk_index] &= ~(1 << bit_position)
112
+
113
+ def to_float(self) -> float:
114
+ """Convert a 64-bit array to a floating-point number.
115
+
116
+ Returns:
117
+ float: The floating-point number represented by the bit array.
118
+ Raises:
119
+ AssertionError: If the bit array is not 64 bits long.
120
+ """
121
+ assert self._length == 64, "Bit array must be 64 bits long."
122
+
123
+ # Convert first chunk directly to bytes
124
+ chunk = self._chunks[0]
125
+ byte_values = bytearray()
126
+ for i in range(8):
127
+ byte = (chunk >> (56 - i * 8)) & 0xFF
128
+ byte_values.append(byte)
129
+
130
+ # Unpack as double precision (64 bits)
131
+ return struct.unpack("!d", bytes(byte_values))[0] # type: ignore
132
+
133
+ def to_int(self) -> int:
134
+ """Convert the bit array to an unsigned integer.
135
+
136
+ Returns:
137
+ int: The integer represented by the bit array.
138
+ """
139
+ result = 0
140
+ for i in range(self._length):
141
+ if self._get_bit(i):
142
+ result |= 1 << (self._length - 1 - i)
143
+ return result
144
+
145
+ def to_signed_int(self) -> int:
146
+ """Convert a bit array into a signed integer using off-set binary
147
+ representation.
148
+
149
+ Returns:
150
+ int: The signed integer represented by the bit array.
151
+ Raises:
152
+ AssertionError: If the bit array is empty.
153
+ """
154
+ assert self._length > 0, "Bit array must not be empty."
155
+
156
+ int_value = self.to_int()
157
+ # Half of the maximum value
158
+ bias = 1 << (self._length - 1)
159
+ # If the sign bit is set, subtract the bias
160
+ return int_value - bias
161
+
162
+ def shift(self, shift_amount: int, fill: bool = False) -> Int64BitArray:
163
+ """Shift the bit array left or right by a specified number of bits.
164
+
165
+ This function shifts the bits in the array, filling in new bits with the
166
+ specified fill value.
167
+ If the value is positive, it shifts left; if negative, it shifts right.
168
+ Fills the new bits with the specified fill value (default is False).
169
+
170
+ Args:
171
+ shift_amount (int): The number of bits to shift. Positive for left shift,
172
+ negative for right shift.
173
+ fill (bool): The value to fill in the new bits created by the shift.
174
+ Defaults to False.
175
+ Returns:
176
+ Int64BitArray: A new BitArray with the bits shifted and filled.
177
+ """
178
+ if shift_amount == 0:
179
+ return self.copy()
180
+
181
+ # Convert to bit list for simplicity (can be optimized later)
182
+ bits = list(self)
183
+
184
+ if abs(shift_amount) > len(bits):
185
+ new_bits = [fill] * len(bits)
186
+ elif shift_amount > 0:
187
+ new_bits = [fill] * shift_amount + bits[:-shift_amount]
188
+ else:
189
+ new_bits = bits[-shift_amount:] + [fill] * (-shift_amount)
190
+
191
+ return Int64BitArray(new_bits)
192
+
193
+ def copy(self) -> Int64BitArray:
194
+ """Create a copy of the bit array.
195
+
196
+ Returns:
197
+ Int64BitArray: A new BitArray with the same bits.
198
+ """
199
+ instance = Int64BitArray.__new__(Int64BitArray)
200
+ instance._length = self._length
201
+ instance._chunks = self._chunks.copy() # This creates a shallow copy
202
+ return instance
203
+
204
+ def __len__(self) -> int:
205
+ """Return the length of the bit array."""
206
+ return self._length
207
+
208
+ @overload
209
+ def __getitem__(self, index: int) -> bool: ...
210
+ @overload
211
+ def __getitem__(self, index: slice) -> Int64BitArray: ...
212
+
213
+ def __getitem__(self, index: int | slice) -> bool | Int64BitArray:
214
+ """Get an item or slice from the bit array."""
215
+ if isinstance(index, slice):
216
+ start, stop, step = index.indices(self._length)
217
+ bits = [self._get_bit(i) for i in range(start, stop, step)]
218
+ return Int64BitArray(bits)
219
+ return self._get_bit(index)
220
+
221
+ @overload
222
+ def __setitem__(self, index: int, value: bool) -> None: ...
223
+ @overload
224
+ def __setitem__(self, index: slice, value: BitArray | list[bool]) -> None: ...
225
+
226
+ def __setitem__(
227
+ self, index: int | slice, value: bool | list[bool] | BitArray
228
+ ) -> None:
229
+ """Set an item or slice in the bit array."""
230
+ if isinstance(index, slice):
231
+ start, stop, step = index.indices(self._length)
232
+ indices = list(range(start, stop, step))
233
+
234
+ if isinstance(value, BitArray):
235
+ values = list(value)
236
+ elif isinstance(value, list):
237
+ values = value
238
+ else:
239
+ raise TypeError("Cannot assign a single bool to a slice")
240
+
241
+ if len(indices) != len(values):
242
+ raise ValueError("Length mismatch in slice assignment")
243
+
244
+ for i, v in zip(indices, values):
245
+ self._set_bit(i, v)
246
+ return
247
+
248
+ if isinstance(value, bool):
249
+ self._set_bit(index, value)
250
+ else:
251
+ raise TypeError("Cannot assign a list or BitArray to a single index")
252
+
253
+ def __iter__(self) -> Iterator[bool]:
254
+ """Iterate over the bits in the array."""
255
+ for i in range(self._length):
256
+ yield self._get_bit(i)
257
+
258
+ def __add__(self, other: BitArray | list[bool]) -> Int64BitArray:
259
+ """Concatenate two bit arrays."""
260
+ if isinstance(other, BitArray):
261
+ return Int64BitArray(list(self) + list(other))
262
+ return Int64BitArray(list(self) + other)
263
+
264
+ def __radd__(self, other: list[bool]) -> Int64BitArray:
265
+ """Reverse concatenation with a list."""
266
+ return Int64BitArray(other + list(self))
267
+
268
+ def __eq__(self, other: object) -> bool:
269
+ """Check equality with another BitArray or list."""
270
+ if isinstance(other, BitArray):
271
+ if len(self) != len(other):
272
+ return False
273
+ return all(a == b for a, b in zip(self, other))
274
+ if isinstance(other, list):
275
+ return list(self) == other
276
+ return False
277
+
278
+ def __bool__(self) -> bool:
279
+ """Return True if any bit is set."""
280
+ return any(chunk != 0 for chunk in self._chunks)
281
+
282
+ def __repr__(self) -> str:
283
+ """Return a string representation of the BitArray."""
284
+ return f"Int64BitArray({list(self)})"
285
+
286
+ def any(self) -> bool:
287
+ """Return True if any bit is set to True."""
288
+ return any(chunk != 0 for chunk in self._chunks)
289
+
290
+ def all(self) -> bool:
291
+ """Return True if all bits are set to True."""
292
+ if self._length == 0:
293
+ return True
294
+
295
+ # Check full chunks
296
+ num_full_chunks = self._length // 64
297
+ for i in range(num_full_chunks):
298
+ if self._chunks[i] != 0xFFFFFFFFFFFFFFFF:
299
+ return False
300
+
301
+ # Check partial chunk if exists
302
+ remaining_bits = self._length % 64
303
+ if remaining_bits > 0:
304
+ expected_pattern = ((1 << remaining_bits) - 1) << (64 - remaining_bits)
305
+ if self._chunks[-1] != expected_pattern:
306
+ return False
307
+
308
+ return True