• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Nic30 / pyMathBitPrecise / d9d2683f-3145-4e9b-8b48-211fa8a4cd64

13 Sep 2025 11:03AM UTC coverage: 66.688% (+0.3%) from 66.371%
d9d2683f-3145-4e9b-8b48-211fa8a4cd64

push

circleci

Nic30
feat(bit_utils): least significant bits manipulation utils

217 of 376 branches covered (57.71%)

Branch coverage included in aggregate %.

15 of 17 new or added lines in 1 file covered. (88.24%)

846 of 1218 relevant lines covered (69.46%)

0.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

46.14
/pyMathBitPrecise/bit_utils.py
1
#!/usr/bin/env python3
2
# -*- coding: UTF-8 -*-
3
import math
1✔
4
from typing import List, Tuple, Generator, Union, Optional, Literal, Sequence
1✔
5

6
from pyMathBitPrecise.utils import grouper
1✔
7

8

9
def mask(bits: int) -> int:
1✔
10
    """
11
    Generate mask of specified size (sequence of '1')
12
    """
13
    return (1 << bits) - 1
1✔
14

15

16
def bit_field(_from: int, to: int) -> int:
1✔
17
    """
18
    Generate int which has bits '_from' to 'to' set to '1'
19

20
    :note: _from 0 to 1 -> '1'
21
    """
22
    w = to - _from
1✔
23
    return mask(w) << _from
1✔
24

25

26
def get_bit(val: int, bitNo: int) -> int:
1✔
27
    """
28
    Get bit from int
29
    """
30
    return (val >> bitNo) & 1
1✔
31

32

33
def get_bit_range(val: int, bitsStart: int, bitsLen: int) -> int:
1✔
34
    """
35
    Get sequence of bits from an int value
36
    """
37
    val >>= bitsStart
1✔
38
    return val & mask(bitsLen)
1✔
39

40

41
def get_single_1_at_position_of_least_significant_0(x: int):
1✔
42
    """
43
    Hacker's Delight: 2nd Edition, 2-1 Manipulating Rightmost Bits
44
    
45
    10100111 -> 00001000
46
    """
47
    assert x >= 0, x
1✔
48
    return ~x & (x + 1)
1✔
49

50

51
def get_single_0_at_position_of_least_significant_1(x: int, width: int):
1✔
52
    """
53
    Hacker's Delight: 2nd Edition, 2-1 Manipulating Rightmost Bits
54
    
55
    10101000 -> 11110111
56
    """
57
    assert x >= 0, x
1✔
58
    res = ~x | (x - 1)
1✔
59
    return to_unsigned(res, width)
1✔
60

61

62
def clean_bit(val: int, bitNo: int) -> int:
1✔
63
    """
64
    Set a specified bit to '0'
65
    """
66
    return val & ~(1 << bitNo)
1✔
67

68

69
def clear_least_significant_1(x: int) -> int:
1✔
70
    """
71
    Hacker's Delight: 2nd Edition, 2-1 Manipulating Rightmost Bits
72
    010110 -> 010100
73
    
74
    :note: can be used for 2**n test
75
    """
76
    # :note: this is equivalent to x - (x & -x)
77
    return x & (x - 1)
1✔
78

79

80
def clear_trailing_1s(x: int) -> int:
1✔
81
    """
82
    Hacker's Delight: 2nd Edition, 2-1 Manipulating Rightmost Bits
83
    10100111 -> 10100000
84
    
85
    :note: can be used for 2**n – 1 test
86
    """
87
    return x & (x + 1)
1✔
88

89

90
def set_bit(val: int, bitNo: int) -> int:
1✔
91
    """
92
    Set a specified bit to '1'
93
    """
94
    return val | (1 << bitNo)
1✔
95

96

97
def set_least_significant_0(x: int) -> int:
1✔
98
    """
99
    Hacker's Delight: 2nd Edition, 2-1 Manipulating Rightmost Bits
100

101
    101001 -> 101011
102
    """
103
    return x | (x + 1)
1✔
104

105

106
def set_trailing_0s(x: int) -> int:
1✔
107
    """
108
    Hacker's Delight: 2nd Edition, 2-1 Manipulating Rightmost Bits
109

110
    10101000 -> 10101111
111
    """
112
    return x | (x - 1)
1✔
113

114

115
def toggle_bit(val: int, bitNo: int) -> int:
1✔
116
    """
117
    Toggle specified bit in int
118
    """
119
    return val ^ (1 << bitNo)
1✔
120

121

122
def set_bit_range(val: int, bitStart: int, bitsLen: int, newBits: int) -> int:
1✔
123
    """
124
    Set specified range of bits in int to a specified value
125
    """
126
    _mask = mask(bitsLen)
1✔
127
    newBits &= _mask
1✔
128

129
    _mask <<= bitStart
1✔
130
    newBits <<= bitStart
1✔
131

132
    return (val & ~_mask) | newBits
1✔
133

134

135
def bit_set_to(val: int, bitNo: int, bitVal: int) -> int:
1✔
136
    """
137
    Set specified bit in int to a specified value
138
    """
139
    if bitVal == 0:
1✔
140
        return clean_bit(val, bitNo)
1✔
141
    elif bitVal == 1:
1!
142
        return set_bit(val, bitNo)
1✔
143
    else:
144
        raise ValueError(("Invalid value of bit to set", bitVal))
×
145

146

147
def byte_mask_to_bit_mask_int(m: int, width: int, byte_width:int=8) -> int:
1✔
148
    """
149
    Expands each bit byte_width times to convert from byte mask to bit mask
150
    """
151
    res = 0
×
152
    mTmp = m
×
153
    byte_mask = mask(byte_width)
×
154
    for i in range(width):
×
155
        b = mTmp & 1
×
156
        if b:
×
157
            res |= byte_mask << (i * byte_width)
×
158
        mTmp >>= 1
×
159

160
    return res
×
161

162

163
def byte_mask_to_bit_mask(m: "Bits3Val", byte_width:int=8) -> "Bits3Val":
1✔
164
    """
165
    Replicate each bit byte_width times
166
    """
167
    res = None
×
168
    for b in m:
×
169
        if res is None:
×
170
            res = b._sext(byte_width)
×
171
        else:
172
            res = b._sext(byte_width)._concat(res)
×
173

174
    return res
×
175

176

177
def bit_mask_to_byte_mask_int(m: int, width: int, byte_width:int=8) -> int:
1✔
178
    """
179
    Compresses all bit in byte to 1 bit to convert from bit mask to byte mask
180
    """
181
    assert width % byte_width == 0
×
182
    mTmp = m
×
183
    res = 0
×
184
    byte_mask = mask(byte_width)
×
185
    for i in range(width // byte_width):
×
186
        B = mTmp & byte_mask
×
187
        if B == byte_mask:
×
188
            res |= 1 << i
×
189
        else:
190
            assert B == 0, "Each byte must be entirely set or entirely unset"
×
191
        mTmp >>= byte_width
×
192

193
    return res
×
194

195

196
def apply_set_and_clear(val: int, set_flag: int, clear_flag: int):
1✔
197
    """
198
    :param val: an input value of the flag(s)
199
    :param set_flag: a mask of bits to set to 1
200
    :param clear_flag: a mask of bits to set to 0
201
    :note: set has higher priority
202

203
    :return: new value of the flag
204
    """
205
    return (val & ~clear_flag) | set_flag
1✔
206

207

208
def apply_write_with_mask(current_data: "Bits3val", new_data: "Bits3val", write_mask: "Bits3val") -> "Bits3val":
1✔
209
    """
210
    :return: an updated value current_data which has bytes defined by write_mask updated from new_data
211
    """
212
    m = byte_mask_to_bit_mask(write_mask)
×
213
    return apply_set_and_clear(current_data, new_data & m, m)
×
214

215

216
def extend_to_width_multiple_of_8(v: "Bits3val") -> "Bits3val":
1✔
217
    """
218
    make width of signal modulo 8 equal to 0
219
    """
220
    w = v._dtype.bit_length()
×
221
    cosest_multiple_of_8 = math.ceil((w // 8) / 8) * 8
×
222
    if cosest_multiple_of_8 == w:
×
223
        return v
×
224
    else:
225
        return v._ext(cosest_multiple_of_8)
×
226

227

228
def align(val: int, lowerBitCntToAlign: int) -> int:
1✔
229
    """
230
    Cut off lower bits to align a int value.
231
    """
232
    val = val >> lowerBitCntToAlign
1✔
233
    return val << lowerBitCntToAlign
1✔
234

235

236
def align_with_known_width(val, width: int, lowerBitCntToAlign: int):
1✔
237
    """
238
    Does same as :func:`~.align` just with the known width of val
239
    """
240
    return val & (mask(width - lowerBitCntToAlign) << lowerBitCntToAlign)
×
241

242

243
def iter_bits(val: int, length: int) -> Generator[Literal[0, 1], None, None]:
1✔
244
    """
245
    Iterate bits in int. LSB first.
246
    """
247
    for _ in range(length):
1✔
248
        yield val & 1
1✔
249
        val >>= 1
1✔
250

251

252
def iter_bits_sequences(val: int, length: int) -> Generator[Tuple[Literal[0, 1], int], None, None]:
1✔
253
    """
254
    Iter tuples (bitVal, number of same bits), lsb first
255
    """
256
    assert length > 0, length
×
257
    assert val >= 0
×
258
    # start of new bit seqence
259
    w = 1
×
260
    valBit = val & 1
×
261
    val >>= 1
×
262
    foundBit = valBit
×
263
    for _ in range(length - 1):
×
264
        # extract single bit from val
265
        valBit = val & 1
×
266
        val >>= 1
×
267
        # check if it fits into current bit sequence
268
        if valBit == foundBit:
×
269
            w += 1
×
270
        else:
271
            # end of sequence of same bits
272
            yield (foundBit, w)
×
273
            foundBit = valBit
×
274
            w = 1
×
275

276
    if w != 0:
×
277
        yield (foundBit, w)
×
278

279

280
def to_signed(val: int, width: int) -> int:
1✔
281
    """
282
    Convert unsigned int to negative int which has same bits set (emulate sign overflow).
283

284
    :note: bits in value are not changed, just python int object
285
        has signed flag set properly. And number is in expected range.
286
    """
287
    if val > 0:
1✔
288
        msb = 1 << (width - 1)
1✔
289
        if val & msb:
1✔
290
            val -= mask(width) + 1
1✔
291
    return val
1✔
292

293

294
def to_unsigned(val, width) -> int:
1✔
295
    if val < 0:
1✔
296
        return val & mask(width)
1✔
297
    else:
298
        return val
1✔
299

300

301
def mask_bytes(val: int, byte_mask: int, mask_bit_length: int) -> int:
1✔
302
    """
303
    Use each bit in byte_mask as a mask for each byte in val.
304

305
    :note: Useful for masking of value for HW interfaces where mask
306
        is represented by a vector of bits where each bit is mask
307
        for byte in data vector.
308
    """
309
    res = 0
1✔
310
    for i, m in enumerate(iter_bits(byte_mask, mask_bit_length)):
1✔
311
        if m:
1✔
312
            res |= (val & 0xff) << (i * 8)
1✔
313
        val >>= 8
1✔
314
    return res
1✔
315

316

317
INT_BASES = {
1✔
318
    "b": 2,
319
    "o": 8,
320
    "d": 10,
321
    "h": 16,
322
}
323

324

325
class ValidityError(ValueError):
1✔
326
    """
327
    Value is not fully defined and thus can not be used
328
    """
329

330

331
def normalize_slice(s: slice, obj_width: int) -> Tuple[int, int]:
1✔
332
    start, stop, step = s.start, s.stop, s.step
1✔
333
    if step is not None and step != -1:
1✔
334
        raise NotImplementedError(s.step)
335
    else:
336
        step = -1
1✔
337
    if stop is None:
1✔
338
        stop = 0
1✔
339
    else:
340
        stop = int(stop)
1✔
341

342
    if start is None:
1✔
343
        start = int(obj_width)
1✔
344
    else:
345
        start = int(start)
1✔
346
    # n...0
347
    if start <= stop:
1✔
348
        raise IndexError(s)
1✔
349
    firstBitNo = stop
1✔
350
    size = start - stop
1✔
351
    if start < 0 or stop < 0 or size < 0 or start > obj_width:
1✔
352
        raise IndexError(s)
1✔
353

354
    return firstBitNo, size
1✔
355

356

357
def reverse_bits(val: int, width: int):
1✔
358
    """
359
    Reverse bits in integer value of specified width
360
    """
361
    v = 0
1✔
362
    for i in range(width):
1✔
363
        v |= (get_bit(val, width - i - 1) << i)
1✔
364
    return v
1✔
365

366

367
def extend_to_size(collection: Sequence, items: int, pad=0):
1✔
368
    toAdd = items - len(collection)
1✔
369
    assert toAdd >= 0
1✔
370
    for _ in range(toAdd):
1✔
371
        collection.append(pad)
1✔
372

373
    return collection
1✔
374

375

376
def rotate_right(v: int, width: int, shAmount:int):
1✔
377
    # https://www.geeksforgeeks.org/rotate-bits-of-an-integer/
378
    assert v >= 0, v
×
379
    assert width > 0, width
×
380
    assert shAmount >= 0, shAmount
×
381
    return (v >> shAmount) | ((v << (width - shAmount)) & mask(width))
×
382

383

384
def rotate_left(v: int, width: int, shAmount:int):
1✔
385
    # https://www.geeksforgeeks.org/rotate-bits-of-an-integer/
386
    assert v >= 0, v
×
387
    assert width > 0, width
×
388
    assert shAmount >= 0, shAmount
×
389
    return ((v << shAmount) & mask(width)) | (v >> (width - shAmount))
×
390

391

392
def bit_list_reversed_endianity(bitList: List[Literal[0, 1]], extend=True):
1✔
393
    w = len(bitList)
1✔
394
    i = w
1✔
395

396
    items = []
1✔
397
    while i > 0:
1✔
398
        # take last 8 bytes or rest
399
        lower = max(i - 8, 0)
1✔
400
        b = bitList[lower:i]
1✔
401
        if extend:
1!
402
            extend_to_size(b, 8)
1✔
403
        items.extend(b)
1✔
404
        i -= 8
1✔
405

406
    return items
1✔
407

408

409
def bit_list_reversed_bits_in_bytes(bitList: List[Literal[0, 1]], extend=None):
1✔
410
    "Byte reflection  (0x0f -> 0xf0)"
411
    w = len(bitList)
1✔
412
    if extend is None:
1!
413
        assert w % 8 == 0
1✔
414
    tmp = []
1✔
415
    for db in grouper(8, bitList, padvalue=0):
1✔
416
        tmp.extend(reversed(db))
1✔
417

418
    if not extend and len(tmp) != w:
1!
419
        rem = w % 8
×
420
        # rm zeros from [0, 0, 0, 0, 0, d[2], d[1], d[0]] like
421
        tmp = tmp[:w - rem] + tmp[-rem:]
×
422

423
    return tmp
1✔
424

425

426
def bytes_to_bit_list_lower_bit_first(bytes_: bytes) -> List[Literal[0, 1]]:
1✔
427
    """
428
    b'\x01' to [1, 0, 0, 0, 0, 0, 0, 0]
429
    """
430
    result: List[Literal[0, 1]] = []
×
431
    for byte in bytes_:
×
432
        for _ in range(8):
×
433
            result.append(byte & 0b1)
×
434
            byte >>= 1
×
435
    return result
×
436

437

438
def bytes_to_bit_list_upper_bit_first(bytes_: bytes) -> List[Literal[0, 1]]:
1✔
439
    """
440
    b'\x01' to [0, 0, 0, 0, 0, 0, 0, 1]
441
    """
442
    result: List[Literal[0, 1]] = []
×
443
    for byte in bytes_:
×
444
        for _ in range(8):
×
445
            result.append((byte & 0x80) >> 7)
×
446
            byte <<= 1
×
447
    return result
×
448

449

450
def byte_list_to_be_int(_bytes: List[Literal[0, 1, 2, 3, 4, 5, 6, 7]]):
1✔
451
    """
452
    In input list LSB first, in result little endian ([1, 0] -> 0x0001)
453
    """
454
    return int_list_to_int(_bytes, 8)
×
455

456

457
def bit_list_to_int(bitList: List[Literal[0, 1]]):
1✔
458
    """
459
    In input list LSB first, in result little endian ([0, 1] -> 0b10)
460
    """
461
    res = 0
1✔
462
    for i, r in enumerate(bitList):
1✔
463
        res |= (r & 0x1) << i
1✔
464
    return res
1✔
465

466

467
def bit_list_to_bytes(bitList: List[Literal[0, 1]]) -> bytes:
1✔
468
    byteCnt = len(bitList) // 8
×
469
    if len(bitList) % 8:
×
470
        byteCnt += 1
×
471
    return bit_list_to_int(bitList).to_bytes(byteCnt, 'big')
×
472

473

474
def int_list_to_int(il: List[int], item_width: int):
1✔
475
    """
476
    [0x0201, 0x0403] -> 0x04030201
477
    """
478
    v = 0
1✔
479
    for i, b in enumerate(il):
1✔
480
        v |= b << (i * item_width)
1✔
481

482
    return v
1✔
483

484

485
def int_to_int_list(v: int, item_width: int, number_of_items: int):
1✔
486
    """
487
    opposite of :func:`~.int_list_to_int`
488
    """
489
    item_mask = mask(item_width)
1✔
490
    res = []
1✔
491
    for _ in range(number_of_items):
1✔
492
        res.append(v & item_mask)
1✔
493
        v >>= item_width
1✔
494

495
    assert v == 0, ("there should be nothing left, the value is larger", v)
1✔
496
    return res
1✔
497

498

499
def reverse_byte_order(val: "Bits3val"):
1✔
500
    """
501
    Reverse byteorder (littleendian/bigendian) of signal or value
502
    """
503
    w = val._dtype.bit_length()
×
504
    i = w
×
505
    items = []
×
506

507
    while i > 0:
×
508
        # take last 8 bytes or rest
509
        lower = max(i - 8, 0)
×
510
        items.append(val[i:lower])
×
511
        i -= 8
×
512

513
    # Concat(*items)
514
    top = None
×
515
    for s in items:
×
516
        if top is None:
×
517
            top = s
×
518
        else:
519
            top = top._concat(s)
×
520
    return top
×
521

522

523
def reverse_byte_order_int(val: int, width: int):
1✔
524
    assert width % 8 == 0, width
×
525
    return int.from_bytes(val.to_bytes(width // 8, "big"), "little")
×
526

527

528
def is_power_of_2(v: Union["Bits3val", int]):
1✔
529
    if isinstance(v, int):
×
530
        assert v > 0
×
NEW
531
        return (v != 0) & (clear_least_significant_1(v) == 0)
×
532
    else:
NEW
533
        return (v != 0) & (clear_least_significant_1(v)._eq(0))
×
534

535

536
def next_power_of_2(v: Union["Bits3val", int], width:Optional[int]=None):
1✔
537
    # depend on the fact that v < 2^width
538
    v = v - 1
×
539
    if isinstance(v, int):
×
540
        assert width is not None
×
541
        v = to_unsigned(v, width)
×
542
    else:
543
        width = v._dtype.bit_length()
×
544

545
    i = 1
×
546
    while True:
×
547
        v |= (v >> i)  # 1, 2, 4, 8, 16 for 32b
×
548
        i <<= 1
×
549
        if i > width // 2:
×
550
            break
×
551

552
    v = v + 1
×
553

554
    if isinstance(v, int):
×
555
        v &= mask(width)
×
556

557
    return v
×
558

559

560
def round_up_to_multiple_of(v: int, divider:int):
1✔
561
    """
562
    Round up the v to be the multiple of divider
563
    """
564
    _v = (v // divider) * divider
×
565
    if _v < v:
×
566
        return _v + divider
×
567
    else:
568
        return _v
×
569

570

571
def round_up_to_power_of_2(x: int):
1✔
572
    assert x >= 0, x
×
573
    if x == 0:
×
574
        return 0
×
575
    return int(2 ** math.ceil(math.log2(x)))
×
576

577

578
def ctlz(Val: int, width: int) -> int:
1✔
579
    """
580
    Count leading zeros
581
    """
582
    if Val == 0:
×
583
        return width
×
584

585
    # Bisection method.
586
    ZeroBits = 0
×
587
    if not is_power_of_2(width):
×
588
        # because alg. works only for pow2 width
589
        _w = next_power_of_2(width, 64)
×
590
        paddingBits = _w - width
×
591
        width = _w
×
592
    else:
593
        paddingBits = 0
×
594

595
    Shift = width >> 1
×
596
    while Shift:
×
597
        Tmp = Val >> Shift
×
598
        if Tmp:
×
599
            Val = Tmp
×
600
        else:
601
            ZeroBits |= Shift
×
602
        Shift >>= 1
×
603
    return ZeroBits - paddingBits
×
604

605

606
def _ctpop_u64(v: int) -> int:
1✔
607
    v = v - ((v >> 1) & 0x5555555555555555)
×
608
    v = (v & 0x3333333333333333) + ((v >> 2) & 0x3333333333333333)
×
609
    v = (v + (v >> 4)) & 0x0F0F0F0F0F0F0F0F
×
610
    return (v * 0x0101010101010101) >> 56
×
611

612

613
def ctpop(val: int, width: int):
1✔
614
    """
615
    count number of 1 in val (population count)
616
    """
617
    res = 0
×
618
    mask_u64 = mask(64)
×
619
    while True:
×
620
        res += _ctpop_u64(val & mask_u64)
×
621
        width -= 64
×
622
        if width <= 0:
×
623
            break
×
624
        val >>= 64
×
625
    return res
×
626

627

628
def cttz(val: int, width:int):
1✔
629
    """
630
    Count trailing zeros
631
    """
632
    if val == 0:
×
633
        return width
×
634
    if val & 0x1:
×
635
        return 0
×
636

637
    # ctpop method: (x & -x).bit_length() - 1
638
    # Bisection method.
639
    ZeroBits = 0
×
640
    if not is_power_of_2(width):
×
641
        width = next_power_of_2(width, 64)  # because alg. works only for pow2  width
×
642
    Shift = width >> 1
×
643
    Mask = mask(width) >> Shift
×
644
    while Shift:
×
645
        if (val & Mask) == 0:
×
646
            val >>= Shift
×
647
            ZeroBits |= Shift
×
648

649
        Shift >>= 1
×
650
        Mask >>= Shift
×
651

652
    return ZeroBits
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc