0

I've created a function that enables you to get a range of bits in a byte, counting bit 0 for the least significant bit (LSb) on the right and 7 for the most significant bit (MSb) on the left for code in C or C++. However, it's not so straightforward to set bits. This can be generalized for short, int, long etc., but for now I'm sticking with bytes.

Given the following:

#include <stdio.h>
#typedef unsigned char BYTE;

BYTE getByteBits(BYTE n, BYTE b) {
    return (n < 8) ? b & ((0x01 << (n + 1)) - 1) : 0;
}

where the bits we want to extract are between bits 0 and n, and b is the full byte. If n is 0 only 0 or 1 is returned for this LSb, if n is 1, values between 0 and 3 can be returned, etc., up to n is 7 for the full byte. Of course in the latter case the code is redundant.

This can be called from main() by using something like:

BYTE num = getByteBits(4, myByte);

which will return the numerical value from the 4 lowest bits. However, I've discovered that this can be generalized to:

BYTE num = getByteBits(n - m, myByte >> m);

which will extract the value returned by the bits from m to n, such that 0 <= m <= n <= 7. This is done by shifting myByte by m bits to the right, which is equivalent to shifting the mask to the left.

However, so far I've been unable to do something similar to set bits using a function with three arguments. The best I can do is to create the function:

BYTE setByteBits(BYTE n, BYTE m, BYTE b, BYTE c) {
    BYTE mask = ((0x01 << (n + 1)) - 1) << m;
    return (b & ~mask) | (c & mask);
}

where n and m are the same as before, b is the value of the original byte, and c is the value of some of the bits we want to change. The update byte is returned. This would be called as:

BYTE num = setByteBits(n - m, m, MyByte, c << m);

but 4 rather than 3 arguments are needed, and rather than shifting myByte by m bits to the right, the mask is shifted to the left together with its complement in the function, as given be the 2nd argument m. Although as far as I can see this works correctly, so far all attempts to do something similar as getByteBits() with 3 arguments have failed.

Does anybody have any idea about this? Also I would appreciate if any bugs can be found in the code. Incidentally, for setByteBits() I made use of the link:

How do you set only certain bits of a byte in C without affecting the rest?

Many thanks.

phuclv
  • 37,963
  • 15
  • 156
  • 475
csharp
  • 464
  • 2
  • 10
  • 19
  • 2
    I think you want to present your code on codereview.stackexchange.com instead of here, because there's lots of ways to improve it, but there is no specific question you're asking. – Ulrich Eckhardt Jul 22 '22 at 07:33
  • 3
    note the existence of [`std::bitset`](https://en.cppreference.com/w/cpp/utility/bitset) and [`std::byte`](https://en.cppreference.com/w/cpp/types/byte). Did you write any (unit)tests for this? If not: you should. – JHBonarius Jul 22 '22 at 07:40
  • 1
    generalization of this code might have pitfalls because of integer promotions. – Swift - Friday Pie Jul 22 '22 at 07:41
  • 2
    Be clear, do you mean "C" or do you mean "C++" they are different languages and the answer you need depends on the choice. For C++ it would be std::bitset, for "C" it would be something else. – Pepijn Kramer Jul 22 '22 at 07:48
  • I don't see how your generalization is more *general*. It just looks more convoluted. – molbdnilo Jul 22 '22 at 07:51
  • also seems it does have problem if it uses the most significant bit. ANd is fragile against overflow. – Swift - Friday Pie Jul 22 '22 at 07:57
  • C and C++ are very different languages. Please select only one and remove the other tag – phuclv Jul 22 '22 at 08:50
  • `#typedef unsigned char BYTE;` is invalid. `BYTE n` That's not a byte, that a bit position. – KamilCuk Jul 22 '22 at 09:10
  • At the moment all code is in C, rather than C++, so std::bitset etc., cannot be used. – csharp Jul 22 '22 at 22:49

1 Answers1

0

so far I've been unable to do something similar to set bits using a function with three arguments

I do not understand your code, because you use so many m n c b one letter variables. Maybe try to be more descriptive? You don't have to write short code, there is no performance gain in that.

When n is the stopping bit position inside the byte, 1 << (n + 1) will give you too big mask. You have to shift 1 of the length of the range of bits, and then that mask shift to the left by the start position. I mixed n with m so many times in your code, I do not know which is which.

The following works, at least for tests I tried:

#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <limits.h>

typedef unsigned char byte;
#define BYTE_BITS  CHAR_BIT
typedef uint_fast8_t bitpos;

/**
 * Set bits in the byte `thebyte`
 * between the range of bit `rangestart` inclusive to `rangestop` exclusive
 * counting from 0 from LSB
 * the the range of these bits inside `masktoset`.
 */
byte setByteBits(bitpos rangestart,
        bitpos rangestop,
        byte thebyte,
        byte masktoset) {
    assert(rangestop <= BYTE_BITS);
    assert(rangestart < rangestop);
    const bitpos rangelen = rangestop - rangestart;
    const byte rangemask = (1u << rangelen) - 1u;
    const byte mask = rangemask << rangestart;
    return (thebyte & ~mask) | (masktoset & mask);
}

int main() {
    const int tests[][5] = {
        { 4,6,0,0xff,0b00110000 },
        { 4,6,0,0b01010101,0b00010000 },
        { 4,6,0,0b01100101,0b00100000 },
        { 4,6,0xff,0b01000101,0b11001111 },
        { 0,8,0,0xff,0xff },
        { 0,8,0,0xab,0xab },
        { 0,4,0xfa,0xab,0xfb },
        { 0,4,0xab,0xcd,0xad },
        { 4,8,0xab,0xcd,0xcb },
        { 4,8,0xef,0xab,0xaf },
    };
    for (size_t i = 0; i <sizeof(tests)/sizeof(*tests); ++i) {
        const int *const t = tests[i];
        const byte r = setByteBits(t[0],t[1],t[2],t[3]);
        fprintf(stderr, "%d (%#02x,%#02x,%#02x,%#02x)->%#02x ?= %#02x\n",
            i,t[0],t[1],t[2],t[3],r,t[4]);
        assert(r == t[4]);
    }
}
KamilCuk
  • 120,984
  • 8
  • 59
  • 111