C语言位运算的常见应用

本文探讨了C++中利用位运算实现的高效编程技巧,如单变量表示多状态、快速乘2和除2运算、哈希函数的位操作优化,以及模运算、无中间值交换和绝对值求解的独特方法。这些技术展示了编程中利用底层原理提高性能的实例。
摘要由CSDN通过智能技术生成

单变量表示多状态

如果你有使用过SDL2库,你一定见过这样的代码

renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);

这其中有一个奇怪的用法

SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC

这里只输入了一个参数,但是就好像是输入了两个

在C++标准库中也有类似的用法

std::fstream fs;
fs.open("a.data",std::ios::in | std::ios::binary);

这是怎么回事呢?我们以std::ios::binary为例,先来看看它在iso_base.h中的定义。

// ...  
  enum _Ios_Openmode 
    { 
      _S_app 		= 1L << 0,
      _S_ate 		= 1L << 1,
      _S_bin 		= 1L << 2,
      _S_in 		= 1L << 3,
      _S_out 		= 1L << 4,
      _S_trunc 		= 1L << 5,
      _S_noreplace 	= 1L << 6,
      _S_ios_openmode_end = 1L << 16,
      _S_ios_openmode_max = __INT_MAX__,
      _S_ios_openmode_min = ~__INT_MAX__
    };
// ...

// ...
    static const openmode binary =	_S_bin;
// ...

可以看出C++标准库的做法是将每一个“标签“用一个64bit(即8byte)整数000...001按位左移不同的位数得到。使该64bit数据的每一位都表示一个标签的真假,一共可以表示64个不同的标签,再用按位或操作即可将多个标签混合到一个值,具体如下表所示。

值1值2两者按位或结果描述
00…00100…01000…011读结果后两位可知,值1,2均表示真
00…00000…01000…010读结果后两位可知,值1,2分别表示假,真
00…00100…00000…001读结果后两位可知,值1,2分别表示真,假
00…00000…00000…000读结果后两位可知,值1,2均表示假

可以写一个C程序来验证上述操作。

#include <stdio.h>

// int32_t 自然是32位二进制
// 一个八进制数表示两个比特位
// 一个十六进制数表示四个比特位
// 例如:
// 0x0 = 0000
// 0xF = 1111
// 0xFF = 1111 1111(空格仅为方便阅读,可去除)
// 0xAC = 1010 1100

int check(int input,int val)
{
    int mark,i;
    for(mark = 0;val != 1;val >>= 1) mark++;
    for(i = 0;i < mark;i++) input >>= 1;
    return (input & val) == 1;
}

int main(void)
{
    int a = 1;                  //bin: 0000 ... 0000 0001   hex: 0x0...01
    int b = 2;                  //bin: 0000 ... 0000 0010   hex: 0x0...02
    int c = 4;                  //bin: 0000 ... 0000 0100   hex: 0x0...04
    int d = 8;                  //bin: 0000 ... 0000 1000   hex: 0x0...08
    int e = 16;                 //bin: 0000 ... 0001 0000   hex: 0x0...10
    int input = a | b | c | d;  //bin: 0000 ... 0000 1111   hex: 0x0...0F
    
    printf("%d\n",check(input,a));
    printf("%d\n",check(input,b));
    printf("%d\n",check(input,c));
    printf("%d\n",check(input,d));
    printf("%d\n",check(input,e));
    return 0;
}

注释中的bin表示二进制,hex表示十六进制,此处赋值时直接使用了十进制。

更快的乘2运算

先来看几个二进制数

二进制十进制
00011
00102
01004
10008

不难看出,亦不难证出一个二进制数按位左移1位即能使其乘以2。

术业有专攻,这种专门乘2的算法理应是要比传统的万用乘法运算符要快的,我们可以通过一个简单的C程序验证这个猜测。

#include <stdio.h>
#include <time.h>

#define PCD 100000

int main(void)
{
    int i,j;
    long long n = 1;
    float s,e;
    s = clock();
    for(i = 0;i < PCD;i++) for(j = 0;j < PCD;j++) n *= 2;
    e = clock();
    printf("%lf sec.\n",(e - s)/CLOCKS_PER_SEC);

    n = 1;
    s = clock();
    for(i = 0;i < PCD;i++) for(j = 0;j < PCD;j++) n <<= 1;
    e = clock();
    printf("%lf sec.\n",(e - s)/CLOCKS_PER_SEC);
}

测试结果如下:

第一次第二次第三次平均
n *= 24.257221 sec4.125867 sec4. 176448 sec4.186512 sec
n <<= 13.519814 sec3.600015 sec3.618779 sec3.579536 sec

确实有所提升,事实上,大多数编译器启用代码优化后都会尝试将n * 2替换为n << 1,以此提升程序性能。

更快的除2运算

原理和上述乘2运算一样,测试代码如下:

#include <stdio.h>
#include <time.h>

#define PCD 100000

int main(void)
{
    int i,j;
    long long n = 10000;
    float s,e;
    s = clock();
    for(i = 0;i < PCD;i++) for(j = 0;j < PCD;j++) n /= 2;
    e = clock();
    printf("%lf sec.\n",(e - s)/CLOCKS_PER_SEC);

    n = 10000;
    s = clock();
    for(i = 0;i < PCD;i++) for(j = 0;j < PCD;j++) n >>= 1;
    e = clock();
    printf("%lf sec.\n",(e - s)/CLOCKS_PER_SEC);
}

测试结果如下:

第一次第二次第三次平均
n /= 27.669598 sec7.514236 sec7.551863 sec7.578565 sec
n >>= 13.414502 sec3.584141 sec3.611094 sec3.536579 sec

同样的,大多编译器也会在开启代码优化时尝试将n /2替换为n >> 1以提升性能。

哈希函数

哈希函数肯定离不开位运算,以FNV-1a 算法为例:

uint32_t fnv1a_32(char* data)
{
    // 32bit version of the algorithm FNV-1a
    static const uint32_t HASH_OFFSET_BASIS_32 = 0x811C9DC5;
    static const uint32_t FNV_PRIME_32         = 16777619;

    uint32_t hash = HASH_OFFSET_BASIS_32;
    int len = strlen(data);
    for(int i = 0; i < len; i++)
    {
        hash *= FNV_PRIME_32;
        hash ^= data[i];
    }

    return hash;
}

详见我的另一篇博文。

位运算的一些奇怪用法

更快但有局限的模运算

#define QUICK_MODE(M,N) ((M) & ((N) - 1))

测试代码如下:

#include <stdio.h>
#include <time.h>

#define QUICK_MODE(M,N) ((M) & ((N) - 1))
#define PCD 100000
#define N 2
// 当且仅当 N = 2^n 时 QUICK_MODE(M,N) 可用,否则计算出错

int main(void)
{
    float s,e;
    int i,j,x1,x2,y = 114514;

    s = clock();
    for(i = 0;i < PCD;i++) for(j = 0;j < PCD;j++) x1 = y % N;
    e = clock();
    printf("%lf sec\n",(e-s)/CLOCKS_PER_SEC);

    s = clock();
    for(i = 0;i < PCD;i++) for(j = 0;j < PCD;j++) x2 = QUICK_MODE(y, N);
    e = clock();
    printf("%lf sec\n",(e-s)/CLOCKS_PER_SEC);

    printf("%s\n",x1 == x2 ? "PASSED":"FAILED");

    return 0;
}

输出如下:

5.105534 sec
4.125605 sec
PASSED

更慢但无中间值的交换

void noval_swap(int* m,int* n)
{
    *m = *m ^ *n;
    *n = *m ^ *n;
    *m = *m ^ *n;
}

测试代码:

#include <stdio.h>
#include <time.h>

#define PCD 100000

void traditional_swap(int* m,int* n)
{
    int buffer = *m;
    *m = *n;
    *n = buffer;
}

void noval_swap(int* m,int* n)
{
    *m = *m ^ *n;
    *n = *m ^ *n;
    *m = *m ^ *n;
}

int check(int* m,int* n)
{
    int _m = *m, _n = *n;
    traditional_swap(m, m);
    noval_swap(&_m,&_n);
    return (_m == *n && _n == *m);
}

int main(void)
{
    float s,e;
    int m = 3,n = 2,i,j;
    printf("%s\n",check(&m,&n)?"PASSED":"FAILED");

    s = clock();
    for(i = 0;i < PCD;i++) for(j = 0;j < PCD;j++) traditional_swap(&m, &n);
    e = clock();
    printf("%lf sec\n",(e-s)/CLOCKS_PER_SEC);

    s = clock();
    for(i = 0;i < PCD;i++) for(j = 0;j < PCD;j++) noval_swap(&m, &n);
    e = clock();
    printf("%lf sec\n",(e-s)/CLOCKS_PER_SEC);
    
    return 0;
}

炫酷但更慢的求绝对值

#define ABS(N) ((((N) >> 31) ^ (N)) - ((N) >> 31))

测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define ABS(N) ((((N) >> 31) ^ (N)) - ((N) >> 31))
#define PCD 100000

int abs(int n)
{
    return n > 0 ? n : -n;
}

int main(void)
{
    int i,j,m,n = -114;
    float s,e;

    s = clock();
    for(i = 0;i < PCD;i++) for(j = 0;j < PCD;j++) m = abs(n);
    e = clock();
    printf("%lf sec.\n",(e - s)/CLOCKS_PER_SEC);

    s = clock();
    for(i = 0;i < PCD;i++) for(j = 0;j < PCD;j++) m = abs(n);
    e = clock();
    printf("%lf sec.\n",(e - s)/CLOCKS_PER_SEC);

    s = clock();
    for(i = 0;i < PCD;i++) for(j = 0;j < PCD;j++) m = ABS(n);
    e = clock();
    printf("%lf sec.\n",(e - s)/CLOCKS_PER_SEC);

    return 0;
}

输出:

4.285493 sec.
4.521450 sec.
5.298813 sec.
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值