How to create a byte out of 8 bool values (and vice versa)?

北战南征 提交于 2019-11-26 17:48:16

The hard way:

unsigned char ToByte(bool b[8])
{
    unsigned char c = 0;
    for (int i=0; i < 8; ++i)
        if (b[i])
            c |= 1 << i;
    return c;
}

And:

void FromByte(unsigned char c, bool b[8])
{
    for (int i=0; i < 8; ++i)
        b[i] = (c & (1<<i)) != 0;
}

Or the cool way:

struct Bits
{
    unsigned b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1;
};
union CBits
{
    Bits bits;
    unsigned char byte;
};

Then you can assign to one member of the union and read from another. But note that the order of the bits in Bits is implementation defined.

Lalaland

You might want to look into std::bitset. It allows you to compactly store booleans as bits, with all of the operators you would expect.

No point fooling around with bit-flipping and whatnot when you can abstract away.

#include <stdint.h>   // to get the uint8_t type

uint8_t GetByteFromBools(const bool eightBools[8])
{
   uint8_t ret = 0;
   for (int i=0; i<8; i++) if (eightBools[i] == true) ret |= (1<<i);
   return ret;
}

void DecodeByteIntoEightBools(uint8_t theByte, bool eightBools[8])
{
   for (int i=0; i<8; i++) eightBools[i] = ((theByte & (1<<i)) != 0);
}

The cool way (using the multiplication technique)

inline uint8_t pack8bools(bool* a)
{
    uint64_t t = *((uint64_t*)a);
    return 0x8040201008040201*t >> 56;
}

void unpack8bools(uint8_t b, bool* a)
{
    auto MAGIC = 0x8040201008040201ULL;
    auto MASK  = 0x8080808080808080ULL;
    *((uint64_t*)a) = ((MAGIC*b) & MASK) >> 7;
}

Of course you may need to make sure that the bool array is correctly 8-byte aligned to avoid performance shoot down and/or UB


How they work?

Suppose we have 8 bools b[0] to b[7] whose least significant bits are named a-h respectively that we want to pack into a single byte. Treating those 8 consecutive bools as one 64-bit word and load them we'll get the bits in reversed order in a little-endian machine. Now we'll do a multiplication (here dots are zero bits)

  |  b7  ||  b6  ||  b4  ||  b4  ||  b3  ||  b2  ||  b1  ||  b0  |
  .......h.......g.......f.......e.......d.......c.......b.......a
x 1000000001000000001000000001000000001000000001000000001000000001
  ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
  ↑......h.↑.....g..↑....f...↑...e....↑..d.....↑.c......↑b.......a
  ↑.....g..↑....f...↑...e....↑..d.....↑.c......↑b.......a
  ↑....f...↑...e....↑..d.....↑.c......↑b.......a
+ ↑...e....↑..d.....↑.c......↑b.......a
  ↑..d.....↑.c......↑b.......a
  ↑.c......↑b.......a
  ↑b.......a
  a       
  ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
= abcdefghxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

The arrows are added so it's easier to see the position of the set bits in the magic number. At this point 8 least significant bits has been put in the top byte, we'll just need to mask the remaining bits out

So the magic number for packing would be 0b1000000001000000001000000001000000001000000001000000001000000001 or 0x8040201008040201. If you're on a big endian machine you'll need to use the magic number 0x0102040810204080 which is calculated in a similar manner

For unpacking we can do a similar multiplication

  |  b7  ||  b6  ||  b4  ||  b4  ||  b3  ||  b2  ||  b1  ||  b0  |
                                                          abcdefgh
x 1000000001000000001000000001000000001000000001000000001000000001
__________________________________________________________________
= h0abcdefgh0abcdefgh0abcdefgh0abcdefgh0abcdefgh0abcdefgh0abcdefgh
& 1000000010000000100000001000000010000000100000001000000010000000
__________________________________________________________________    
= h0000000g0000000f0000000e0000000d0000000c0000000b0000000a0000000

After multiplying we have the needed bits at the most significant positions, so we need to mask out irrelevant bits and shift the remainings to the least significant positions. The output will be the bytes contain a to h in little endian.


The efficient way

On newer x86 CPUs with BMI2 there are PEXT and PDEP instructions for this purpose. The pack8bools function above can be replaced with

_pext_u64(*((uint64_t*)a), 0x0101010101010101ULL);

And the unpack8bools function can be implemented as

_pdep_u64(b, 0x0101010101010101ULL);
bool a,b,c,d,e,f,g,h;
//do stuff
char y= a<<7 | b<<6 | c<<5 | d<<4 | e <<3 | f<<2 | g<<1 | h;//merge

although you are probably better off using a bitset

http://www.cplusplus.com/reference/stl/bitset/bitset/

ScarletAmaranth

There is no way to pack 8 bool variables into one byte. There is a way packing 8 logical true/false states in a single byte using Bitmasking.

Fabián Heredia Montiel

You would use the bitwise shift operation and casting to archive it. a function could work like this:

unsigned char toByte(bool *bools)
{
    unsigned char byte = \0;
    for(int i = 0; i < 8; ++i) byte |= ((unsigned char) bools[i]) << i;
    return byte;
}

Thanks Christian Rau for the correction s!

I'd like to note that type punning through unions is UB in C++ (as rodrigo does in his answer. The safest way to do that is memcpy()

struct Bits
{
    unsigned b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1;
};

unsigned char toByte(Bits b){
    unsigned char ret;
    memcpy(&ret, &b, 1);
    return ret;
}

As others have said, the compiler is smart enough to optimize out memcpy().

BTW, this is the way that Boost does type punning.

Even with C++ I am using this header file:

#ifndef __bit_h__
#define __bit_h__

#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include <stdint.h>
#endif

#ifndef BITWISE_OPERATIONS_TYPE
#define BITWISE_OPERATIONS_TYPE uint16_t
#endif

// gives a value with only the nth bit set
// usage: int flags = 10000b;
//        bool enabled = (flags & BIT(4)) ? true : false; // result is true
#define BIT(n) (((BITWISE_OPERATIONS_TYPE) 1) << (n))

// gives the input with the nth bit set
// usage: flags = BIT_SET(flags, 3);
// result: flags = 0b11000
#define BIT_SET(in, n) (in | BIT(n))

// gives the input with the nth bit clear
// usage: flags = BIT_CLR(flags, 3);
// result: flags = 0b10000
#define BIT_CLR(in, n) (in & ~BIT(n))

// gives the nth bit only of the input
// usage: bool both_clr = !(BIT_GET(flags1, 3) & BIT_GET(flags2, 3));
// result: both_clr = true (lets say `flags1, flags2 = 0, 0`)
#define BIT_GET(in, n) (in & BIT(n))

// gives 1 if the nth bit of the input is set else gives 0
// usage: if(IS_BIT_SET(flags, 3)) { /*... it will not run */ }
#define IS_BIT_SET(in, n) (BIT_GET(in, n) > 0)

static inline BITWISE_OPERATIONS_TYPE bit(unint8_t n) {
    return (((BITWISE_OPERATIONS_TYPE) 1) << n); }

static inline BITWISE_OPERATIONS_TYPE bit_set(BITWISE_OPERATIONS_TYPE in, unint8_t n) {
    return (in | bit(n)); }

static inline BITWISE_OPERATIONS_TYPE bit_clr(BITWISE_OPERATIONS_TYPE in, unint8_t n) {
    return (in & ~bit(n)); }

static inline BITWISE_OPERATIONS_TYPE bit_get(BITWISE_OPERATIONS_TYPE in, unint8_t n) {
    return (in & bit(n)); }

static inline unint8_t is_bit_set(BITWISE_OPERATIONS_TYPE in, unint8_t n) {
    return (bit_get(in, n) > 0); }

#ifdef __cplusplus
}
#endif

#endif // __bit_h__

Simple and easy to understand, without class definitions and you can modify this file freely, according to your needs ... for example you can change the uint16_t to uint32_t or to uint64_t. Although both macros and functions will produce almost if not just identical code ... depending on the architecture of the machine you are using to compile this code.

So as a solution to your problem, this is how you can pack and unpack them:

static char pack8bit(bool* bools) {
// `char` for an 8bit return (output) value and `bool*` for the input 8 bools ... should be unrolled args ?!?!
    char buff = 0;
    for(unsigned char i = 0; i < 8; ++i)
        buff = (bools[i]) ? bit_set(buff, i) : bit_clr(buff, i);
    return buff;
}

static void unpack8bit(const char& from, bool* bools) {
// `from` for the packed input and `bool*` for the output 8 bools ... should be unrolled args ?!?!
    for(unsigned char i = 0; i < 8; ++i)
        bools[i] = is_bit_set(from, i) ? true : false;
}

I know this is a very late answer ...

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!