如何从位移中获得"lost"位?

How do I get the "lost" bit from a bit shift?

本文关键字:lost      更新时间:2023-10-16

我想对变量进行位移位,并将移位后的位存储在布尔值中。

类似于:

unsigned int i = 1;
bool b = rshift(&i); // i now equals 0 and b is set to true

如何才能做到这一点?

您必须在移位前捕获位

bool rshift(unsigned int* p) {
    bool ret = (*p) & 1;
    *p >>= 1;
    return ret;
}

您没有。你必须在换班前测试一下:

bool
rshift( unsigned& i )
{
    bool results = (i & 0x01) != 0;
    i >>= 1;
    return results;
}

(这对于右移来说很简单。对于左移,你必须知道字中的位数。)

要对任何移位和任何类型执行此操作,请反转操作(检查是否丢失了什么)。

template <typename T> bool rshift(T& val) {
  T const original = val;
  return ((val >>= 1) << 1) != original;
}
template <typename T> bool lshift(T& val) {
  T const original = val;
  return ((val <<= 1) >> 1) != original;
}

如果您正在寻找一个旋转函数,您可以尝试以下操作。

首先,有一个元函数来获取要旋转的值的位数(注意:8*sizeof(T)不可移植;标准只规定至少8位):

#include <climits>
template <typename T>
struct bits { enum { value = CHAR_BIT * sizeof(T) }; };

接下来,定义一个向右旋转的函数。它通过按预期向右移动,向左移动来向上移动否则会被取消的东西:

template <unsigned int N, typename T>
T rotate_right (T value)
{
    enum { left = bits<T>::value - N };
    return (value>>N) | (value<<left);
}

具有测试诊断功能,并测试:

#include <iostream>
template <typename T>
void print_bits (std::ostream &os, T value)
{
    for (unsigned int i=bits<T>::value; i!=0; --i)
        os << ((value>>(i-1))&1);
}
    int main () {
    char c = 1,
         c_ = rotate_right<1>(c);
    unsigned long long ull = 0xF0F0C0C050503030, 
                       ull_ = rotate_right<63>(ull);
    std::cout << "c ="; print_bits(std::cout, c);  std::cout << 'n';
    std::cout << "c'="; print_bits(std::cout, c_); std::cout << 'n';
    std::cout << "ull ="; print_bits(std::cout, ull);  std::cout << 'n';
    std::cout << "ull'="; print_bits(std::cout, ull_); std::cout << 'n';
}

输出:

c =00000001
c'=10000000
ull =1111000011110000110000001100000001010000010100000011000000110000
ull'=1110000111100001100000011000000010100000101000000110000001100001

左旋转可以类似地实现,或者根据右旋转来实现。

至于性能,g++检测到习惯用法,并在x86和amd64上使用旋转指令。

g++ -std=c++0x -S main.cc
cat main.s
...
sarl %eax
...

以下是迄今为止提出的建议的替代方案:

bool rshift(int32_t &i) {
  long long i2 = (static_cast<int64_t>(i) << 31);
  i = (i2 >> 32);
  return static_cast<int32_t>(i2);
}

这假设intint现在通常的意思,即int32_t。它将其复制到int64_t中,然后执行移位,将较高的32位复制到i中,然后将较低的32位(包含移位的1)作为布尔返回。

我不知道这是否比其他方法更快。这种通用方法的附加优点是,它可以用于检索多个比特,例如,如果您偏移了2个或更多比特。

这里有一个用于位抓取的小型c#演示:

using System;
class BitToBoolArray {
  static void Main() {
  
  ushort guiMask = 0b1100_1100_0011_0011;
  
    bool[] boxEnabled = new bool[16]; 
    
    for (int i=0;i<16;guiMask>>=1,++i) 
    {
        
        bool bit =  (guiMask & 0x0001) == 1 ;
        boxEnabled[i] = bit;
        Console.WriteLine("rnBit position: {0} , Bit: {1}",i,bit);
    }
    
  }
}

右移是从变量中获取位的最常见方法,问题是结果是向后的,这有点无聊。有一种方法可以获得变量中的二进制值,即:

该策略是屏蔽一部分比特,就像在右移中一样,但现在针对最重要的比特,二进制常数很好地满足了这一要求。

unsigned int number = 2863311530;
unsigned int mostSignificantBit = 0b10000000000000000000000000000000;
// Masking a number by a binary that only has the
// most significant bit set will result in the
// same binary if the most significant bit of the 
// variable was 1. 
if((number & mostSignificantBit) == mostSignificantBit){
    printf("1");
}else{
    printf("0");
}

转储变量的整个二进制内容如下所示:

// The good part of using bitwise operations is that
// signed and unsigned values really don't matter,
// the only thing that bitwise operations care about is binary.
int number = -123456;
unsigned int mostSignificantBit = 0b10000000000000000000000000000000;
for(int i = 0; i < 32; i++){
    if((number & mostSignificantBit) == mostSignificantBit){
        printf("1");
    }else{
        printf("0");
    }
    number = number << 1;
}
printf("n");
// Result: 11111111111111100001110111000000
// this binary is really the 2's complement of
// the number -123456

一些警报

前面的例子效果很好!然而,不同的体系结构/平台甚至编译器可以定义int可以容纳的不同大小的总比特数。好消息是,这并不难处理。标题<stdint.h>实现保持固定位数的数据类型,例如:

#include <stdint.h>
int main(void){
    int16_t sixteenBitsInt;
    uint16_t unsignedSixteenBitsInt;
    int32_t thirtyTwoBitsInt;
    uint32_t unsignedThirtyTwoBitsInt;
    // ...
    // ...
    // ...
    return 0;
}

但是,如果出于任何原因你不想使用固定的数据长度,那么即使在跨平台环境中存在不同大小的整数,你也可以使用更多的数据长度来实现这一点。

这里的策略是得到一个只有最高有效位集的二进制,而不管变量的大小。按照二进制逻辑,如果你得到一个变量的最大无符号值,除以2,再加上值1,你就会得到这个必要的二进制,例如:

#include <stdio.h>
#include <stdint.h>
#include <limits.h>
#define MOST_SIGNIFICANT_BIT UINT_MAX / 2 + 1
#define TRUE 1
#define FALSE 0
typedef unsigned char bool;
int main(void){
    int number = -123456;
    unsigned int mostSignificantBit = MOST_SIGNIFICANT_BIT;
    bool stopFlag = FALSE;
    // By the reason of the numbers of bits may be variable,
    // we need divide a UINT_MAX until reach 0 to be sure that
    // there's no more bits to be shifted.
    unsigned int max = UINT_MAX;
    while(stopFlag == FALSE){
        if((number & mostSignificantBit) == mostSignificantBit){
            printf("1");
        }else{
            printf("0");
        }
        // Updating the max here allows the
        // algorithm to run more one time after 
        // reaching the "0". Doing this calculation
        // inside the while would make the program
        // let one bit behind inside the number.
        max /= 2;
        if(max == 0){
            stopFlag = TRUE;
        }
        number = number << 1;
    }
    printf("n");
    // Result: 11111111111111100001110111000000
    // This binary is the 2's complement of the
    // number -123456, and is the same of the previous
    // example of variable dumping.
    return 0;
}

宏UINT_MAX及其同级将跨平台保持不同的值,目的是指定给定数据类型的最大值,从而允许跨平台实现。使用固定长度的数据类型确实更容易,但这也是一个有用的解决方案。