c++ - studio - tipos de datos float
¿Cómo crear un byte de 8 valores bool(y viceversa)? (9)
La manera fresca (utilizando la técnica de multiplicación ).
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;
}
Por supuesto, es posible que deba asegurarse de que la matriz bool esté alineada correctamente con 8 bytes para evitar el disparo y / o UB del rendimiento.
¿Cómo trabajan ellos?
Supongamos que tenemos 8 bools b[0]
a b[7]
cuyos bits menos significativos se denominan ah respectivamente que queremos empaquetar en un solo byte. Al tratar esos 8 bool
consecutivos como una palabra de 64 bits y cargarlos, obtendremos los bits en orden inverso en una máquina little-endian. Ahora haremos una multiplicación (aquí los puntos son cero 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
Las flechas se agregan para que sea más fácil ver la posición de los bits establecidos en el número mágico. En este punto, se han colocado 8 bits menos significativos en el byte superior, solo necesitaremos enmascarar los bits restantes
Entonces, el número mágico para el empaque sería 0b1000000001000000001000000001000000001000000001000000001000000001
o 0x8040201008040201
. Si estás en una máquina big endian, necesitarás usar el número mágico 0x0102040810204080
que se calcula de manera similar
Para desembalar podemos hacer una multiplicación similar.
| b7 || b6 || b4 || b4 || b3 || b2 || b1 || b0 |
abcdefgh
x 1000000001000000001000000001000000001000000001000000001000000001
__________________________________________________________________
= h0abcdefgh0abcdefgh0abcdefgh0abcdefgh0abcdefgh0abcdefgh0abcdefgh
& 1000000010000000100000001000000010000000100000001000000010000000
__________________________________________________________________
= h0000000g0000000f0000000e0000000d0000000c0000000b0000000a0000000
Después de multiplicar tenemos los bits necesarios en las posiciones más significativas, por lo que necesitamos enmascarar los bits irrelevantes y desplazar los restantes a las posiciones menos significativas. La salida será que los bytes contengan una a h en little endian.
La forma eficiente
En las nuevas CPUs x86 con BMI2 hay instrucciones PEXT y PDEP para este propósito. La función pack8bools
anterior puede ser reemplazada por
_pext_u64(*((uint64_t*)a), 0x0101010101010101ULL);
Y la función unpack8bools
se puede implementar como
_pdep_u64(b, 0x0101010101010101ULL);
Tengo 8 variables bool
, y quiero "fusionarlas" en un byte.
¿Hay un método fácil / preferido para hacer esto?
¿Qué hay al revés, decodificar un byte en 8 valores booleanos separados?
Vengo asumiendo que no es una pregunta irrazonable, pero como no pude encontrar documentación relevante a través de Google, es probable que sea otro de esos casos "nonono: toda intuición es incorrecta".
El camino difícil:
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;
}
Y:
void FromByte(unsigned char c, bool b[8])
{
for (int i=0; i < 8; ++i)
b[i] = (c & (1<<i)) != 0;
}
O la manera fresca:
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;
};
Luego puede asignar a un miembro de la unión y leer de otro. Pero tenga en cuenta que el orden de los bits en Bits
es la implementación definida.
Es posible que desee mirar en std::bitset
. Le permite almacenar de forma compacta los booleanos como bits, con todos los operadores que espera.
No tiene sentido perder el tiempo con el cambio de bits y otras cosas cuando puede abstraerse.
Incluso con C ++ estoy usando este archivo de encabezado:
#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 uint_fast64_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(unint_fast8_t n) {
return (((BITWISE_OPERATIONS_TYPE) 1) << n); }
static inline BITWISE_OPERATIONS_TYPE bit_set(BITWISE_OPERATIONS_TYPE in, unint_fast8_t n) {
return (in | bit(n)); }
static inline BITWISE_OPERATIONS_TYPE bit_clr(BITWISE_OPERATIONS_TYPE in, unint_fast8_t n) {
return (in & ~bit(n)); }
static inline BITWISE_OPERATIONS_TYPE bit_get(BITWISE_OPERATIONS_TYPE in, unint_fast8_t n) {
return (in & bit(n)); }
static inline unint_fast8_t is_bit_set(BITWISE_OPERATIONS_TYPE in, unint_fast8_t n) {
return (bit_get(in, n) > 0); }
#ifdef __cplusplus
}
#endif
#endif // __bit_h__
Simple y fácil de entender, sin definiciones de clase y puede modificar este archivo libremente, de acuerdo con sus necesidades ... por ejemplo, puede cambiar el uint_fast64_t
por uint_fast32_t
para permitir que el compilador use un lugar apropiado con acceso rápido que tenga al menos 32 bits de tamaño en lugar de 64bits. Aunque tanto las macros como las funciones producirán casi un código idéntico ... dependiendo de la arquitectura de la máquina que esté utilizando para compilar este código.
Entonces, como solución a su problema, puede crear métodos de get
y set
como este:
bool get(const uint_fast8_t& nth) { // or `const unsigned char&` or `const char&`
return IS_BIT_SET(this->somewhere, nth);
}
void set(const uint_fast8_t& nth) { // or `const unsigned char&` or `const char&`
this->flags = BIT_SET(this->somewhere, nth);
}
Y así es como puedes pack
y unpack
:
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;
}
Sé que esta es una respuesta muy tardía ...
Me gustaría señalar que el tipo punning a través de union
s es UB en C ++ (como lo hace rodrigo en su respuesta . La forma más segura de hacerlo es 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;
}
Como han dicho otros, el compilador es lo suficientemente inteligente como para optimizar memcpy()
.
Por cierto, esta es la forma en que Boost tipea el punning.
No hay forma de empaquetar 8 variables bool
en un byte. Hay una forma de empaquetar 8 estados lógicos de verdadero / falso en un solo byte utilizando en.wikipedia.org/wiki/Bitwise_operation .
Usaría la operación de cambio a nivel de bit y la conversión para archivarla. Una función podría funcionar así:
unsigned char toByte(bool *bools)
{
unsigned char byte = /0;
for(int i = 0; i < 8; ++i) byte |= ((unsigned char) bools[i]) << i;
return byte;
}
Gracias Christian Rau por las correcciones s !
#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);
}
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
Aunque probablemente estés mejor usando un conjunto de bits