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

Nic30 / pyMathBitPrecise / 697bca74-2893-4324-97a5-dd0e956ca48b

23 Dec 2025 12:19PM UTC coverage: 66.747% (-0.3%) from 67.014%
697bca74-2893-4324-97a5-dd0e956ca48b

push

circleci

Nic30
feat(bit_utils): separate_least_significant_1, ctlo, ctto

224 of 390 branches covered (57.44%)

Branch coverage included in aggregate %.

3 of 6 new or added lines in 1 file covered. (50.0%)

103 existing lines in 1 file now uncovered.

878 of 1261 relevant lines covered (69.63%)

0.7 hits per line

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

45.47
/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 separate_least_significant_1(x: int):
1✔
116
    """
117
    1100 -> 0100
118
    """
NEW
119
    return x & -x
×
120

121

122
def toggle_bit(val: int, bitNo: int) -> int:
1✔
123
    """
124
    Toggle specified bit in int
125
    """
126
    return val ^ (1 << bitNo)
1✔
127

128

129
def set_bit_range(val: int, bitStart: int, bitsLen: int, newBits: int) -> int:
1✔
130
    """
131
    Set specified range of bits in int to a specified value
132
    """
133
    _mask = mask(bitsLen)
1✔
134
    newBits &= _mask
1✔
135

136
    _mask <<= bitStart
1✔
137
    newBits <<= bitStart
1✔
138

139
    return (val & ~_mask) | newBits
1✔
140

141

142
def bit_set_to(val: int, bitNo: int, bitVal: int) -> int:
1✔
143
    """
144
    Set specified bit in int to a specified value
145
    """
146
    if bitVal == 0:
1✔
147
        return clean_bit(val, bitNo)
1✔
148
    elif bitVal == 1:
1!
149
        return set_bit(val, bitNo)
1✔
150
    else:
151
        raise ValueError(("Invalid value of bit to set", bitVal))
×
152

153

154
def byte_mask_to_bit_mask_int(m: int, width: int, byte_width:int=8) -> int:
1✔
155
    """
156
    Expands each bit byte_width times to convert from byte mask to bit mask
157
    """
158
    res = 0
×
159
    mTmp = m
×
160
    byte_mask = mask(byte_width)
×
161
    for i in range(width):
×
162
        b = mTmp & 1
×
163
        if b:
×
164
            res |= byte_mask << (i * byte_width)
×
165
        mTmp >>= 1
×
166

167
    return res
×
168

169

170
def byte_mask_to_bit_mask(m: "Bits3Val", byte_width:int=8) -> "Bits3Val":
1✔
171
    """
172
    Replicate each bit byte_width times
173
    """
174
    res = None
×
175
    for b in m:
×
176
        if res is None:
×
177
            res = b._sext(byte_width)
×
178
        else:
179
            res = b._sext(byte_width)._concat(res)
×
180

181
    return res
×
182

183

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

200
    return res
×
201

202

203
def apply_set_and_clear(val: int, set_flag: int, clear_flag: int):
1✔
204
    """
205
    :param val: an input value of the flag(s)
206
    :param set_flag: a mask of bits to set to 1
207
    :param clear_flag: a mask of bits to set to 0
208
    :note: set has higher priority
209

210
    :return: new value of the flag
211
    """
212
    return (val & ~clear_flag) | set_flag
1✔
213

214

215
def apply_write_with_mask(current_data: "Bits3val", new_data: "Bits3val", write_mask: "Bits3val") -> "Bits3val":
1✔
216
    """
217
    :return: an updated value current_data which has bytes defined by write_mask updated from new_data
218
    """
219
    m = byte_mask_to_bit_mask(write_mask)
×
220
    return apply_set_and_clear(current_data, new_data & m, m)
×
221

222

223
def extend_to_width_multiple_of_8(v: "Bits3val") -> "Bits3val":
1✔
224
    """
225
    make width of signal modulo 8 equal to 0
226
    """
227
    w = v._dtype.bit_length()
×
228
    cosest_multiple_of_8 = math.ceil((w // 8) / 8) * 8
×
229
    if cosest_multiple_of_8 == w:
×
230
        return v
×
231
    else:
232
        return v._ext(cosest_multiple_of_8)
×
233

234

235
def align(val: int, lowerBitCntToAlign: int) -> int:
1✔
236
    """
237
    Cut off lower bits to align a int value.
238
    """
239
    val = val >> lowerBitCntToAlign
1✔
240
    return val << lowerBitCntToAlign
1✔
241

242

243
def align_with_known_width(val, width: int, lowerBitCntToAlign: int):
1✔
244
    """
245
    Does same as :func:`~.align` just with the known width of val
246
    """
247
    return val & (mask(width - lowerBitCntToAlign) << lowerBitCntToAlign)
×
248

249

250
def iter_bits(val: int, length: int) -> Generator[Literal[0, 1], None, None]:
1✔
251
    """
252
    Iterate bits in int. LSB first.
253
    """
254
    for _ in range(length):
1✔
255
        yield val & 1
1✔
256
        val >>= 1
1✔
257

258

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

283
    if w != 0:
×
284
        yield (foundBit, w)
×
285

286

287
def to_signed(val: int, width: int) -> int:
1✔
288
    """
289
    Convert unsigned int to negative int which has same bits set (emulate sign overflow).
290

291
    :note: bits in value are not changed, just python int object
292
        has signed flag set properly. And number is in expected range.
293
    """
294
    if val > 0:
1✔
295
        msb = 1 << (width - 1)
1✔
296
        if val & msb:
1✔
297
            val -= mask(width) + 1
1✔
298
    return val
1✔
299

300

301
def to_unsigned(val, width) -> int:
1✔
302
    if val < 0:
1✔
303
        return val & mask(width)
1✔
304
    else:
305
        return val
1✔
306

307

308
def mask_bytes(val: int, byte_mask: int, mask_bit_length: int) -> int:
1✔
309
    """
310
    Use each bit in byte_mask as a mask for each byte in val.
311

312
    :note: Useful for masking of value for HW interfaces where mask
313
        is represented by a vector of bits where each bit is mask
314
        for byte in data vector.
315
    """
316
    res = 0
1✔
317
    for i, m in enumerate(iter_bits(byte_mask, mask_bit_length)):
1✔
318
        if m:
1✔
319
            res |= (val & 0xff) << (i * 8)
1✔
320
        val >>= 8
1✔
321
    return res
1✔
322

323

324
INT_BASES = {
1✔
325
    "b": 2,
326
    "o": 8,
327
    "d": 10,
328
    "h": 16,
329
}
330

331

332
class ValidityError(ValueError):
1✔
333
    """
334
    Value is not fully defined and thus can not be used
335
    """
336

337

338
def normalize_slice(s: slice, obj_width: int) -> Tuple[int, int]:
1✔
339
    start, stop, step = s.start, s.stop, s.step
1✔
340
    if step is not None and step != -1:
1✔
341
        raise NotImplementedError(s.step)
342
    else:
343
        step = -1
1✔
344
    if stop is None:
1✔
345
        stop = 0
1✔
346
    else:
347
        stop = int(stop)
1✔
348

349
    if start is None:
1✔
350
        start = int(obj_width)
1✔
351
    else:
352
        start = int(start)
1✔
353
    # n...0
354
    if start <= stop:
1✔
355
        raise IndexError(s)
1✔
356
    firstBitNo = stop
1✔
357
    size = start - stop
1✔
358
    if start < 0 or stop < 0 or size < 0 or start > obj_width:
1✔
359
        raise IndexError(s)
1✔
360

361
    return firstBitNo, size
1✔
362

363

364
def reverse_bits(val: int, width: int):
1✔
365
    """
366
    Reverse bits in integer value of specified width
367
    """
368
    v = 0
1✔
369
    for i in range(width):
1✔
370
        v |= (get_bit(val, width - i - 1) << i)
1✔
371
    return v
1✔
372

373

374
def extend_to_size(collection: Sequence, items: int, pad=0):
1✔
375
    toAdd = items - len(collection)
1✔
376
    assert toAdd >= 0
1✔
377
    for _ in range(toAdd):
1✔
378
        collection.append(pad)
1✔
379

380
    return collection
1✔
381

382

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

390

391
def rotate_left(v: int, width: int, shAmount:int):
1✔
392
    # https://www.geeksforgeeks.org/rotate-bits-of-an-integer/
393
    assert v >= 0, v
×
394
    assert width > 0, width
×
395
    assert shAmount >= 0, shAmount
×
396
    return ((v << shAmount) & mask(width)) | (v >> (width - shAmount))
×
397

398

399
def bit_list_reversed_endianity(bitList: List[Literal[0, 1]], extend=True):
1✔
400
    w = len(bitList)
1✔
401
    i = w
1✔
402

403
    items: list[Literal[0, 1]] = []
1✔
404
    while i > 0:
1✔
405
        # take last 8 bits or rest
406
        lower = max(i - 8, 0)
1✔
407
        b = bitList[lower:i]
1✔
408
        if extend:
1!
409
            extend_to_size(b, 8)
1✔
410
        items.extend(b)
1✔
411
        i -= 8
1✔
412

413
    return items
1✔
414

415

416
def bit_list_reversed_bits_in_bytes(bitList: List[Literal[0, 1]], extend=None):
1✔
417
    "Byte reflection  (0x0f -> 0xf0)"
418
    w = len(bitList)
1✔
419
    if extend is None:
1!
420
        assert w % 8 == 0
1✔
421

422
    tmp: list[Literal[0, 1]] = []
1✔
423
    for db in grouper(8, bitList, padvalue=0):
1✔
424
        tmp.extend(reversed(db))
1✔
425

426
    if not extend and len(tmp) != w:
1!
427
        rem = w % 8
×
428
        # rm zeros from [0, 0, 0, 0, 0, d[2], d[1], d[0]] like
429
        tmp = tmp[:w - rem] + tmp[-rem:]
×
430

431
    return tmp
1✔
432

433

434
def bytes_to_bit_list_lower_bit_first(bytes_: bytes) -> List[Literal[0, 1]]:
1✔
435
    """
436
    b'\x01' to [1, 0, 0, 0, 0, 0, 0, 0]
437
    """
438
    result: List[Literal[0, 1]] = []
×
439
    for byte in bytes_:
×
440
        for _ in range(8):
×
441
            result.append(byte & 0b1)
×
442
            byte >>= 1
×
443
    return result
×
444

445

446
def bytes_to_bit_list_upper_bit_first(bytes_: bytes) -> List[Literal[0, 1]]:
1✔
447
    """
448
    b'\x01' to [0, 0, 0, 0, 0, 0, 0, 1]
449
    """
450
    result: List[Literal[0, 1]] = []
×
451
    for byte in bytes_:
×
452
        for _ in range(8):
×
453
            result.append((byte & 0x80) >> 7)
×
454
            byte <<= 1
×
455
    return result
×
456

457

458
def byte_list_to_be_int(_bytes: List[Literal[0, 1, 2, 3, 4, 5, 6, 7]]):
1✔
459
    """
460
    In input list LSB first, in result little endian ([1, 0] -> 0x0001)
461
    """
462
    return int_list_to_int(_bytes, 8)
×
463

464

465
def bit_list_to_int(bitList: List[Literal[0, 1]]):
1✔
466
    """
467
    In input list LSB first, in result little endian ([0, 1] -> 0b10)
468
    """
469
    res = 0
1✔
470
    for i, r in enumerate(bitList):
1✔
471
        res |= (r & 0x1) << i
1✔
472
    return res
1✔
473

474

475
def bit_list_to_bytes(bitList: List[Literal[0, 1]]) -> bytes:
1✔
476
    byteCnt = len(bitList) // 8
×
477
    if len(bitList) % 8:
×
478
        byteCnt += 1
×
479
    return bit_list_to_int(bitList).to_bytes(byteCnt, 'big')
×
480

481

482
def int_list_to_int(il: List[int], item_width: int):
1✔
483
    """
484
    [0x0201, 0x0403] -> 0x04030201
485
    """
486
    v = 0
1✔
487
    for i, b in enumerate(il):
1✔
488
        v |= b << (i * item_width)
1✔
489

490
    return v
1✔
491

492

493
def int_to_int_list(v: int, item_width: int, number_of_items: int):
1✔
494
    """
495
    opposite of :func:`~.int_list_to_int`
496
    """
497
    item_mask = mask(item_width)
1✔
498
    res = []
1✔
499
    for _ in range(number_of_items):
1✔
500
        res.append(v & item_mask)
1✔
501
        v >>= item_width
1✔
502

503
    assert v == 0, ("there should be nothing left, the value is larger", v)
1✔
504
    return res
1✔
505

506

507
def reverse_byte_order(val: "Bits3val"):
1✔
508
    """
509
    Reverse byteorder (littleendian/bigendian) of signal or value
510
    """
511
    w = val._dtype.bit_length()
×
512
    i = w
×
513
    items = []
×
514

515
    while i > 0:
×
516
        # take last 8 bytes or rest
517
        lower = max(i - 8, 0)
×
518
        items.append(val[i:lower])
×
519
        i -= 8
×
520

521
    # Concat(*items)
522
    top = None
×
523
    for s in items:
×
524
        if top is None:
×
525
            top = s
×
526
        else:
527
            top = top._concat(s)
×
528
    return top
×
529

530

531
def reverse_byte_order_int(val: int, width: int):
1✔
532
    assert width % 8 == 0, width
×
533
    return int.from_bytes(val.to_bytes(width // 8, "big"), "little")
×
534

535

536
def is_power_of_2(v: Union["Bits3val", int]):
1✔
537
    if isinstance(v, int):
×
538
        assert v > 0
×
539
        return (v != 0) & (clear_least_significant_1(v) == 0)
×
540
    else:
541
        return (v != 0) & (clear_least_significant_1(v)._eq(0))
×
542

543

544
def next_power_of_2(v: Union["Bits3val", int], width:Optional[int]=None):
1✔
545
    # depend on the fact that v < 2^width
546
    v = v - 1
×
547
    if isinstance(v, int):
×
548
        if v < 0:
×
549
            assert width is not None
×
550
            v = to_unsigned(v, width)
×
551
        elif width is None:
×
552
            width = v.bit_length() + 1
×
553
    else:
554
        width = v._dtype.bit_length()
×
555

556
    i = 1
×
557
    while True:
×
558
        v |= (v >> i)  # 1, 2, 4, 8, 16 for 32b
×
559
        i <<= 1
×
560
        if i > (width // 2):
×
561
            break
×
562

563
    v = v + 1
×
564

565
    if isinstance(v, int):
×
566
        v &= mask(width)
×
567

568
    return v
×
569

570

571
def round_up_to_multiple_of(v: int, divider:int):
1✔
572
    """
573
    Round up the v to be the multiple of divider
574
    """
575
    _v = (v // divider) * divider
×
576
    if _v < v:
×
577
        return _v + divider
×
578
    else:
579
        return _v
×
580

581

582
def round_up_to_power_of_2(x: int):
1✔
583
    assert x >= 0, x
×
584
    if x == 0:
×
585
        return 0
×
586
    return int(2 ** math.ceil(math.log2(x)))
×
587

588

589
def ctlz(val: int, width: int) -> int:
1✔
590
    """
591
    Count leading zeros
592
    """
593
    if val == 0:
×
594
        return width
×
595

596
    # Bisection method.
597
    ZeroBits = 0
×
598
    if not is_power_of_2(width):
×
599
        # because alg. works only for pow2 width
600
        _w = next_power_of_2(width, 64)
×
601
        paddingBits = _w - width
×
602
        width = _w
×
603
    else:
604
        paddingBits = 0
×
605

606
    Shift = width >> 1
×
607
    while Shift:
×
608
        Tmp = val >> Shift
×
609
        if Tmp:
×
610
            val = Tmp
×
611
        else:
612
            ZeroBits |= Shift
×
613
        Shift >>= 1
×
614
    return ZeroBits - paddingBits
×
615

616

617
def ctlo(val: int, width: int) -> int:
1✔
618
    """
619
    Count leading ones
620
    """
NEW
621
    return ctlz(~val, width)
×
622

623

624
def _ctpop_u64(v: int) -> int:
1✔
625
    v = v - ((v >> 1) & 0x5555555555555555)
×
626
    v = (v & 0x3333333333333333) + ((v >> 2) & 0x3333333333333333)
×
627
    v = (v + (v >> 4)) & 0x0F0F0F0F0F0F0F0F
×
628
    return (v * 0x0101010101010101) >> 56
×
629

630

631
def ctpop(val: int, width: int):
1✔
632
    """
633
    count number of 1 in val (population count)
634
    """
635
    res = 0
×
636
    mask_u64 = mask(64)
×
637
    while True:
×
638
        res += _ctpop_u64(val & mask_u64)
×
639
        width -= 64
×
640
        if width <= 0:
×
641
            break
×
642
        val >>= 64
×
643
    return res
×
644

645

646
def cttz(val: int, width: int):
1✔
647
    """
648
    Count trailing zeros
649
    """
650
    if val == 0:
×
651
        return width
×
652
    if val & 0x1:
×
653
        return 0
×
654

655
    # ctpop method: (x & -x).bit_length() - 1
656
    # Bisection method.
657
    ZeroBits = 0
×
658
    if not is_power_of_2(width):
×
659
        width = next_power_of_2(width, 64)  # because alg. works only for pow2  width
×
660
    Shift = width >> 1
×
661
    Mask = mask(width) >> Shift
×
662
    while Shift:
×
663
        if (val & Mask) == 0:
×
664
            val >>= Shift
×
665
            ZeroBits |= Shift
×
666

667
        Shift >>= 1
×
668
        Mask >>= Shift
×
669

670
    return ZeroBits
×
671

672

673
def ctto(val: int):
1✔
674
    """
675
    Count trailing ones
676
    """
NEW
677
    return cttz(~val, val.bit_length())
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc