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.
@@ -0,0 +1,202 @@
1
+ """List-based BitArray implementation for the flexfloat package.
2
+
3
+ This implementation is best for small or dynamically sized bit arrays.
4
+ Bit order: LSB-first (least significant bit at index 0, increasing to MSB).
5
+
6
+ Example:
7
+ from flexfloat.bitarray import ListBoolBitArray
8
+ ba = ListBoolBitArray([True, False, True])
9
+ print(list(ba))
10
+ # Output: [True, False, True]
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import struct
16
+ from typing import Iterator, overload
17
+
18
+ from .bitarray import BitArray
19
+ from .bitarray_mixins import BitArrayCommonMixin
20
+
21
+
22
+ class ListBoolBitArray(BitArrayCommonMixin):
23
+ """A bit array class that encapsulates a list of booleans with utility methods.
24
+
25
+ This implementation uses a list of boolean values to represent the bits,
26
+ allowing for dynamic resizing and easy manipulation of individual bits.
27
+ """
28
+
29
+ def __init__(self, bits: list[bool] | None = None):
30
+ """Initializes a ListBoolBitArray.
31
+
32
+ Args:
33
+ bits (list[bool] | None, optional): Initial list of boolean values. Defaults
34
+ to empty list.
35
+ """
36
+ super().__init__()
37
+ if bits is not None:
38
+ self._bits = bits
39
+ else:
40
+ self._bits = []
41
+
42
+ @classmethod
43
+ def from_bits(cls, bits: list[bool] | None = None) -> "ListBoolBitArray":
44
+ """Creates a BitArray from a list of boolean values.
45
+
46
+ Args:
47
+ bits (list[bool] | None, optional): List of boolean values. Defaults to
48
+ None, which creates an empty BitArray.
49
+
50
+ Returns:
51
+ ListBoolBitArray: A BitArray created from the bits.
52
+ """
53
+ return cls(bits)
54
+
55
+ @classmethod
56
+ def zeros(cls, length: int) -> "ListBoolBitArray":
57
+ """Creates a BitArray filled with zeros.
58
+
59
+ Args:
60
+ length (int): The length of the bit array.
61
+
62
+ Returns:
63
+ ListBoolBitArray: A BitArray filled with False values.
64
+ """
65
+ return cls([False] * length)
66
+
67
+ @classmethod
68
+ def ones(cls, length: int) -> "ListBoolBitArray":
69
+ """Creates a BitArray filled with ones.
70
+
71
+ Args:
72
+ length (int): The length of the bit array.
73
+
74
+ Returns:
75
+ ListBoolBitArray: A BitArray filled with True values.
76
+ """
77
+ return cls([True] * length)
78
+
79
+ def to_int(self) -> int:
80
+ """Converts the bit array to an unsigned integer (LSB-first).
81
+
82
+ Returns:
83
+ int: The integer represented by the bit array.
84
+ """
85
+ return sum((1 << i) for i, bit in enumerate(self._bits) if bit)
86
+
87
+ def copy(self) -> "ListBoolBitArray":
88
+ """Creates a copy of the bit array.
89
+
90
+ Returns:
91
+ ListBoolBitArray: A new BitArray with the same bits.
92
+ """
93
+ return ListBoolBitArray(self._bits.copy())
94
+
95
+ def to_float(self) -> float:
96
+ """Converts a 64-bit array to a floating-point number (LSB-first).
97
+
98
+ Returns:
99
+ float: The floating-point number represented by the bit array.
100
+
101
+ Raises:
102
+ AssertionError: If the bit array is not 64 bits long.
103
+ """
104
+ assert len(self._bits) == 64, "Bit array must be 64 bits long."
105
+ byte_values = bytearray()
106
+ for i in range(0, 64, 8):
107
+ byte = 0
108
+ for j in range(8):
109
+ if self._bits[i + j]:
110
+ byte |= 1 << j # LSB-first
111
+ byte_values.append(byte)
112
+ float_value = struct.unpack("<d", bytes(byte_values))[0]
113
+ return float_value # type: ignore
114
+
115
+ def __len__(self) -> int:
116
+ """Returns the length of the bit array.
117
+
118
+ Returns:
119
+ int: The number of bits in the array.
120
+ """
121
+ return len(self._bits)
122
+
123
+ @overload
124
+ def __getitem__(self, index: int) -> bool: ...
125
+ @overload
126
+ def __getitem__(self, index: slice) -> ListBoolBitArray: ...
127
+
128
+ def __getitem__(self, index: int | slice) -> bool | ListBoolBitArray:
129
+ """Get a bit or a slice of bits as a new ListBoolBitArray."""
130
+ if isinstance(index, slice):
131
+ return ListBoolBitArray.from_bits(self._bits[index])
132
+ return self._bits[index]
133
+
134
+ @overload
135
+ def __setitem__(self, index: int, value: bool) -> None: ...
136
+ @overload
137
+ def __setitem__(self, index: slice, value: BitArray | list[bool]) -> None: ...
138
+
139
+ def __setitem__(
140
+ self, index: int | slice, value: bool | list[bool] | BitArray
141
+ ) -> None:
142
+ """Sets an item or slice in the bit array.
143
+
144
+ Args:
145
+ index (int or slice): The index or slice to set.
146
+ value (bool or list[bool] or BitArray): The value(s) to assign.
147
+
148
+ Raises:
149
+ TypeError: If value type does not match index type.
150
+ """
151
+ if isinstance(index, slice):
152
+ if isinstance(value, BitArray):
153
+ self._bits[index] = list(value)
154
+ elif isinstance(value, list):
155
+ self._bits[index] = value
156
+ else:
157
+ raise TypeError("Cannot assign a single bool to a slice")
158
+ return
159
+ if isinstance(value, bool):
160
+ self._bits[index] = value
161
+ else:
162
+ raise TypeError("Cannot assign a list or BitArray to a single index")
163
+
164
+ def __iter__(self) -> Iterator[bool]:
165
+ """Iterates over the bits in the array.
166
+
167
+ Yields:
168
+ bool: The next bit in the array.
169
+ """
170
+ return iter(self._bits)
171
+
172
+ def __add__(self, other: BitArray | list[bool]) -> "ListBoolBitArray":
173
+ """Concatenates two bit arrays.
174
+
175
+ Args:
176
+ other (BitArray or list[bool]): The other bit array or list to concatenate.
177
+
178
+ Returns:
179
+ ListBoolBitArray: The concatenated bit array.
180
+ """
181
+ if isinstance(other, BitArray):
182
+ return ListBoolBitArray(self._bits + list(other))
183
+ return ListBoolBitArray(self._bits + other)
184
+
185
+ def __radd__(self, other: list[bool]) -> "ListBoolBitArray":
186
+ """Reverse concatenation with a list.
187
+
188
+ Args:
189
+ other (list[bool]): The list to concatenate before this bit array.
190
+
191
+ Returns:
192
+ ListBoolBitArray: The concatenated bit array.
193
+ """
194
+ return ListBoolBitArray(other + self._bits)
195
+
196
+ def __repr__(self) -> str:
197
+ """Returns a string representation of the BitArray.
198
+
199
+ Returns:
200
+ str: String representation of the BitArray.
201
+ """
202
+ return f"ListBoolBitArray({self._bits})"
@@ -0,0 +1,333 @@
1
+ """Memory-efficient int64-based BitArray implementation for the flexfloat package.
2
+
3
+ This implementation is ideal for large bit arrays, as it packs 64 bits per integer.
4
+ Bit order: LSB-first (least significant bit at index 0, increasing to MSB).
5
+
6
+ Example:
7
+ from flexfloat.bitarray import ListInt64BitArray
8
+ ba = ListInt64BitArray([0b10101010], length=8)
9
+ print(list(ba))
10
+ # Output: [False, True, False, True, False, True, False, True]
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import struct
16
+ from typing import Iterator, overload
17
+
18
+ from .bitarray import BitArray
19
+ from .bitarray_mixins import BitArrayCommonMixin
20
+
21
+
22
+ class ListInt64BitArray(BitArrayCommonMixin):
23
+ """A memory-efficient bit array class using a list of int64 values.
24
+
25
+ This implementation packs 64 bits per integer, making it more memory efficient
26
+ for large bit arrays compared to the boolean list implementation.
27
+ """
28
+
29
+ def __init__(self, chunks: list[int] | None = None, length: int = 0):
30
+ """Initializes a ListInt64BitArray.
31
+
32
+ Args:
33
+ chunks (list[int] | None, optional): Initial list of int64 chunks. Defaults
34
+ to empty list.
35
+ length (int, optional): The amount of bits in the array. Defaults to 0.
36
+
37
+ Raises:
38
+ ValueError: If length is negative.
39
+ """
40
+ super().__init__()
41
+ chunks = chunks or []
42
+ if length < 0:
43
+ raise ValueError("Length must be non-negative")
44
+ self._length: int = length
45
+ self._chunks: list[int] = chunks
46
+
47
+ @classmethod
48
+ def from_bits(cls, bits: list[bool] | None = None) -> "ListInt64BitArray":
49
+ """Creates a BitArray from a list of boolean values.
50
+
51
+ Args:
52
+ bits (list[bool] | None, optional): List of boolean values in LSB-first
53
+ order. Defaults to None, which creates an empty BitArray.
54
+
55
+ Returns:
56
+ ListInt64BitArray: A BitArray created from the bits.
57
+ """
58
+ if bits is None:
59
+ return cls()
60
+ chunks: list[int] = []
61
+
62
+ for i in range(0, len(bits), 64):
63
+ chunk = 0
64
+ chunk_end = min(i + 64, len(bits))
65
+ for j in range(i, chunk_end):
66
+ if bits[j]:
67
+ chunk |= 1 << (j - i)
68
+ chunks.append(chunk)
69
+
70
+ return cls(chunks, len(bits))
71
+
72
+ @classmethod
73
+ def zeros(cls, length: int) -> "ListInt64BitArray":
74
+ """Creates a BitArray filled with zeros.
75
+
76
+ Args:
77
+ length (int): The length of the bit array.
78
+
79
+ Returns:
80
+ ListInt64BitArray: A BitArray filled with False values.
81
+ """
82
+ return cls([0] * ((length + 63) // 64), length)
83
+
84
+ @classmethod
85
+ def ones(cls, length: int) -> "ListInt64BitArray":
86
+ """Creates a BitArray filled with ones.
87
+
88
+ Args:
89
+ length (int): The length of the bit array.
90
+
91
+ Returns:
92
+ ListInt64BitArray: A BitArray filled with True values.
93
+ """
94
+ chunks = [0xFFFFFFFFFFFFFFFF] * (length // 64)
95
+ if length % 64 > 0:
96
+ partial_chunk = (1 << (length % 64)) - 1
97
+ chunks.append(partial_chunk)
98
+ return cls(chunks, length)
99
+
100
+ def _get_bit(self, index: int) -> bool:
101
+ """Gets a single bit at the specified index (LSB-first).
102
+
103
+ Args:
104
+ index (int): The bit index (LSB-first).
105
+
106
+ Returns:
107
+ bool: The value of the bit at the specified index.
108
+
109
+ Raises:
110
+ IndexError: If index is out of range.
111
+ """
112
+ if index < 0 or index >= self._length:
113
+ raise IndexError("Bit index out of range")
114
+ chunk_index = index // 64
115
+ bit_index = index % 64
116
+ bit_position = bit_index # LSB-first
117
+ return bool(self._chunks[chunk_index] & (1 << bit_position))
118
+
119
+ def _set_bit(self, index: int, value: bool) -> None:
120
+ """Sets a single bit at the specified index (LSB-first).
121
+
122
+ Args:
123
+ index (int): The bit index (LSB-first).
124
+ value (bool): The value to set.
125
+
126
+ Raises:
127
+ IndexError: If index is out of range.
128
+ """
129
+ if index < 0 or index >= self._length:
130
+ raise IndexError("Bit index out of range")
131
+ chunk_index = index // 64
132
+ bit_index = index % 64
133
+ bit_position = bit_index # LSB-first
134
+ mask = 1 << bit_position
135
+ self._chunks[chunk_index] ^= (-value ^ self._chunks[chunk_index]) & mask
136
+
137
+ def to_int(self) -> int:
138
+ """Converts the bit array to an unsigned integer (LSB-first).
139
+
140
+ Returns:
141
+ int: The integer represented by the bit array.
142
+ """
143
+ result = 0
144
+ for i in range(self._length):
145
+ if self._get_bit(i):
146
+ result |= 1 << i
147
+ return result
148
+
149
+ def to_float(self) -> float:
150
+ """Converts a 64-bit array to a floating-point number (LSB-first).
151
+
152
+ Returns:
153
+ float: The floating-point number represented by the bit array.
154
+
155
+ Raises:
156
+ AssertionError: If the bit array is not 64 bits long.
157
+ """
158
+ assert self._length == 64, "Bit array must be 64 bits long."
159
+ chunk = self._chunks[0]
160
+ byte_values = bytearray()
161
+ for i in range(8):
162
+ byte = (chunk >> (i * 8)) & 0xFF # LSB-first
163
+ byte_values.append(byte)
164
+ float_value = struct.unpack("<d", bytes(byte_values))[0]
165
+ return float_value # type: ignore
166
+
167
+ def copy(self) -> "ListInt64BitArray":
168
+ """Creates a copy of the bit array.
169
+
170
+ Returns:
171
+ ListInt64BitArray: A new BitArray with the same bits.
172
+ """
173
+ return ListInt64BitArray(self._chunks.copy(), self._length)
174
+
175
+ def __len__(self) -> int:
176
+ """Returns the length of the bit array.
177
+
178
+ Returns:
179
+ int: The number of bits in the array.
180
+ """
181
+ return self._length
182
+
183
+ @overload
184
+ def __getitem__(self, index: int) -> bool: ...
185
+ @overload
186
+ def __getitem__(self, index: slice) -> "ListInt64BitArray": ...
187
+
188
+ def __getitem__(self, index: int | slice) -> bool | ListInt64BitArray:
189
+ """Get a bit or a slice of bits as a new ListInt64BitArray."""
190
+ if isinstance(index, slice):
191
+ start, stop, step = index.indices(self._length)
192
+ bits = [self._get_bit(i) for i in range(start, stop, step)]
193
+ return ListInt64BitArray.from_bits(bits)
194
+ return self._get_bit(index)
195
+
196
+ @overload
197
+ def __setitem__(self, index: int, value: bool) -> None: ...
198
+ @overload
199
+ def __setitem__(self, index: slice, value: BitArray | list[bool]) -> None: ...
200
+
201
+ def __setitem__(
202
+ self, index: int | slice, value: bool | list[bool] | BitArray
203
+ ) -> None:
204
+ """Sets an item or slice in the bit array.
205
+
206
+ Args:
207
+ index (int or slice): The index or slice to set.
208
+ value (bool or list[bool] or BitArray): The value(s) to assign.
209
+
210
+ Raises:
211
+ TypeError: If value type does not match index type.
212
+ ValueError: If value length does not match slice length.
213
+ """
214
+ if isinstance(index, slice):
215
+ start, stop, step = index.indices(self._length)
216
+ indices = list(range(start, stop, step))
217
+
218
+ if isinstance(value, BitArray):
219
+ values = list(value)
220
+ elif isinstance(value, list):
221
+ values = value
222
+ else:
223
+ raise TypeError("Cannot assign a single bool to a slice")
224
+
225
+ if len(indices) != len(values):
226
+ raise ValueError("Length mismatch in slice assignment")
227
+
228
+ for i, v in zip(indices, values):
229
+ self._set_bit(i, v)
230
+ return
231
+
232
+ if isinstance(value, bool):
233
+ self._set_bit(index, value)
234
+ else:
235
+ raise TypeError("Cannot assign a list or BitArray to a single index")
236
+
237
+ def __iter__(self) -> Iterator[bool]:
238
+ """Iterates over the bits in the array.
239
+
240
+ Yields:
241
+ bool: The next bit in the array.
242
+ """
243
+ for i in range(self._length):
244
+ yield self._get_bit(i)
245
+
246
+ def __add__(self, other: BitArray | list[bool]) -> "ListInt64BitArray":
247
+ """Concatenates two bit arrays.
248
+
249
+ Args:
250
+ other (BitArray or list[bool]): The other bit array or list to concatenate.
251
+
252
+ Returns:
253
+ ListInt64BitArray: The concatenated bit array.
254
+ """
255
+ if isinstance(other, BitArray):
256
+ return ListInt64BitArray.from_bits(list(self) + list(other))
257
+ return ListInt64BitArray.from_bits(list(self) + other)
258
+
259
+ def __radd__(self, other: list[bool]) -> "ListInt64BitArray":
260
+ """Reverse concatenation with a list.
261
+
262
+ Args:
263
+ other (list[bool]): The list to concatenate before this bit array.
264
+
265
+ Returns:
266
+ ListInt64BitArray: The concatenated bit array.
267
+ """
268
+ return ListInt64BitArray.from_bits(other + list(self))
269
+
270
+ def __eq__(self, other: object) -> bool:
271
+ """Checks equality with another BitArray or list.
272
+
273
+ Args:
274
+ other (object): The object to compare with.
275
+
276
+ Returns:
277
+ bool: True if equal, False otherwise.
278
+ """
279
+ if isinstance(other, BitArray):
280
+ if len(self) != len(other):
281
+ return False
282
+ return all(a == b for a, b in zip(self, other))
283
+ if isinstance(other, list):
284
+ return list(self) == other
285
+ return False
286
+
287
+ def __bool__(self) -> bool:
288
+ """Returns True if any bit is set.
289
+
290
+ Returns:
291
+ bool: True if any bit is set, False otherwise.
292
+ """
293
+ return any(chunk != 0 for chunk in self._chunks)
294
+
295
+ def __repr__(self) -> str:
296
+ """Returns a string representation of the BitArray.
297
+
298
+ Returns:
299
+ str: String representation of the BitArray.
300
+ """
301
+ return f"ListInt64BitArray({list(self)})"
302
+
303
+ def any(self) -> bool:
304
+ """Returns True if any bit is set to True.
305
+
306
+ Returns:
307
+ bool: True if any bit is set to True, False otherwise.
308
+ """
309
+ return any(chunk != 0 for chunk in self._chunks)
310
+
311
+ def all(self) -> bool:
312
+ """Returns True if all bits are set to True.
313
+
314
+ Returns:
315
+ bool: True if all bits are set to True, False otherwise.
316
+ """
317
+ if self._length == 0:
318
+ return True
319
+
320
+ # Check full chunks
321
+ num_full_chunks = self._length // 64
322
+ for i in range(num_full_chunks):
323
+ if self._chunks[i] != 0xFFFFFFFFFFFFFFFF:
324
+ return False
325
+
326
+ # Check partial chunk if exists
327
+ remaining_bits = self._length % 64
328
+ if remaining_bits > 0:
329
+ expected_pattern = (1 << remaining_bits) - 1
330
+ if self._chunks[-1] != expected_pattern:
331
+ return False
332
+
333
+ return True
@@ -0,0 +1,156 @@
1
+ """Mixin classes providing common BitArray functionality.
2
+
3
+ This module provides mixins for BitArray implementations, offering default methods that
4
+ rely on the BitArray protocol. Bit order is always LSB-first.
5
+
6
+ Example:
7
+ class MyBitArray(BitArrayCommonMixin):
8
+ ... # implement BitArray protocol methods
9
+ ba = MyBitArray.from_bits([True, False])
10
+ print(list(ba))
11
+ # Output: [True, False]
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import struct
17
+ from typing import Any, Iterable
18
+
19
+ from .bitarray import BitArray
20
+
21
+
22
+ class BitArrayCommonMixin(BitArray):
23
+ """Mixin providing common methods that can be implemented using the BitArray
24
+ protocol.
25
+
26
+ Bit order: LSB-first (least significant bit at index 0, increasing to MSB).
27
+
28
+ This mixin provides default implementations for methods that can be expressed in
29
+ terms of the core BitArray protocol methods (__iter__, __len__, etc.).
30
+
31
+ Classes using this mixin must implement the BitArray protocol.
32
+ """
33
+
34
+ @classmethod
35
+ def from_float(cls, value: float) -> BitArray:
36
+ """Create a BitArray from a float value."""
37
+ packed = struct.pack("<d", value)
38
+ bits = [bool((byte >> bit) & 1) for byte in packed for bit in range(8)]
39
+ return cls.from_bits(bits)
40
+
41
+ @classmethod
42
+ def from_signed_int(cls, value: int, length: int) -> BitArray:
43
+ """Create a BitArray from a signed integer value."""
44
+ half = 1 << (length - 1)
45
+ max_value = half - 1
46
+ min_value = -half
47
+
48
+ assert (
49
+ min_value <= value <= max_value
50
+ ), "Value out of range for specified length."
51
+
52
+ unsigned_value = value + half
53
+ bits = [(unsigned_value >> i) & 1 == 1 for i in range(length)]
54
+ return cls.from_bits(bits)
55
+
56
+ @classmethod
57
+ def parse_bitarray(cls, bitstring: Iterable[str]) -> BitArray:
58
+ """Parses a string of bits (with optional spaces) into a BitArray instance."""
59
+ return cls.from_bits([c == "1" for c in bitstring if c in "01"])
60
+
61
+ def __str__(self) -> str:
62
+ """Returns a string representation of the bits (LSB-first, index 0 is
63
+ rightmost).
64
+
65
+ Returns:
66
+ str: String representation of the bits.
67
+ """
68
+ return "".join("1" if bit else "0" for bit in reversed(list(self)))
69
+
70
+ def __eq__(self, other: Any) -> bool:
71
+ """Checks equality with another BitArray or list.
72
+
73
+ Args:
74
+ other (Any): The object to compare with.
75
+
76
+ Returns:
77
+ bool: True if equal, False otherwise.
78
+ """
79
+ if hasattr(other, "__iter__") and hasattr(other, "__len__"):
80
+ if len(self) != len(other): # type: ignore
81
+ return False
82
+ return all(a == b for a, b in zip(self, other)) # type: ignore
83
+ return False
84
+
85
+ def __bool__(self) -> bool:
86
+ """Returns True if any bit is set.
87
+
88
+ Returns:
89
+ bool: True if any bit is set, False otherwise.
90
+ """
91
+ return self.any()
92
+
93
+ def any(self) -> bool:
94
+ """Returns True if any bit is set to True.
95
+
96
+ Returns:
97
+ bool: True if any bit is set to True, False otherwise.
98
+ """
99
+ return any(self)
100
+
101
+ def all(self) -> bool:
102
+ """Returns True if all bits are set to True.
103
+
104
+ Returns:
105
+ bool: True if all bits are set to True, False otherwise.
106
+ """
107
+ return all(self)
108
+
109
+ def count(self, value: bool = True) -> int:
110
+ """Counts the number of bits set to the specified value.
111
+
112
+ Args:
113
+ value (bool, optional): The value to count. Defaults to True.
114
+
115
+ Returns:
116
+ int: The number of bits set to the specified value.
117
+ """
118
+ return sum(1 for bit in self if bit == value)
119
+
120
+ def reverse(self) -> BitArray:
121
+ """Returns a new BitArray with the bits in reverse order."""
122
+ return self.from_bits(list(reversed(self)))
123
+
124
+ def to_signed_int(self) -> int:
125
+ """Converts a bit array into a signed integer using off-set binary
126
+ representation.
127
+
128
+ Returns:
129
+ int: The signed integer represented by the bit array.
130
+
131
+ Raises:
132
+ AssertionError: If the bit array is empty.
133
+ """
134
+ assert len(self) > 0, "Bit array must not be empty."
135
+
136
+ int_value: int = self.to_int()
137
+ # Half of the maximum value
138
+ bias = 1 << (len(self) - 1)
139
+ # Subtract the bias to get the signed value
140
+ return int_value - bias
141
+
142
+ def shift(self, shift_amount: int, fill: bool = False) -> BitArray:
143
+ """Shifts the bit array left or right by a specified number of bits."""
144
+ if shift_amount == 0:
145
+ return self.copy()
146
+
147
+ bits = list(self)
148
+
149
+ if abs(shift_amount) > len(bits):
150
+ new_bits = [fill] * len(bits)
151
+ elif shift_amount > 0: # Right shift
152
+ new_bits = bits[shift_amount:] + [fill] * shift_amount
153
+ else: # Left shift
154
+ new_bits = [fill] * (-shift_amount) + bits[:shift_amount]
155
+
156
+ return self.from_bits(new_bits)