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

Nic30 / pyMathBitPrecise / 0771434c-5cf1-4677-be49-c335c680dc83

17 Nov 2025 04:15PM UTC coverage: 67.014% (-0.3%) from 67.303%
0771434c-5cf1-4677-be49-c335c680dc83

push

circleci

Nic30
feat(bit_utils): next_power_of_2 width required only for negative ints

222 of 384 branches covered (57.81%)

Branch coverage included in aggregate %.

0 of 6 new or added lines in 1 file covered. (0.0%)

871 of 1247 relevant lines covered (69.85%)

0.7 hits per line

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

45.41
/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: list[Literal[0, 1]] = []
1✔
397
    while i > 0:
1✔
398
        # take last 8 bits 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

415
    tmp: list[Literal[0, 1]] = []
1✔
416
    for db in grouper(8, bitList, padvalue=0):
1✔
417
        tmp.extend(reversed(db))
1✔
418

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

424
    return tmp
1✔
425

426

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

438

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

450

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

457

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

467

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

474

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

483
    return v
1✔
484

485

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

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

499

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

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

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

523

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

528

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

536

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

549
    i = 1
×
550
    while True:
×
551
        v |= (v >> i)  # 1, 2, 4, 8, 16 for 32b
×
552
        i <<= 1
×
NEW
553
        if i > (width // 2):
×
554
            break
×
555

556
    v = v + 1
×
557

558
    if isinstance(v, int):
×
559
        v &= mask(width)
×
560

561
    return v
×
562

563

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

574

575
def round_up_to_power_of_2(x: int):
1✔
576
    assert x >= 0, x
×
577
    if x == 0:
×
578
        return 0
×
579
    return int(2 ** math.ceil(math.log2(x)))
×
580

581

582
def ctlz(Val: int, width: int) -> int:
1✔
583
    """
584
    Count leading zeros
585
    """
586
    if Val == 0:
×
587
        return width
×
588

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

599
    Shift = width >> 1
×
600
    while Shift:
×
601
        Tmp = Val >> Shift
×
602
        if Tmp:
×
603
            Val = Tmp
×
604
        else:
605
            ZeroBits |= Shift
×
606
        Shift >>= 1
×
607
    return ZeroBits - paddingBits
×
608

609

610
def _ctpop_u64(v: int) -> int:
1✔
611
    v = v - ((v >> 1) & 0x5555555555555555)
×
612
    v = (v & 0x3333333333333333) + ((v >> 2) & 0x3333333333333333)
×
613
    v = (v + (v >> 4)) & 0x0F0F0F0F0F0F0F0F
×
614
    return (v * 0x0101010101010101) >> 56
×
615

616

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

631

632
def cttz(val: int, width:int):
1✔
633
    """
634
    Count trailing zeros
635
    """
636
    if val == 0:
×
637
        return width
×
638
    if val & 0x1:
×
639
        return 0
×
640

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

653
        Shift >>= 1
×
654
        Mask >>= Shift
×
655

656
    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