一文了解【机器数 & 位运算】

一篇文章了解 机器数 & 位运算

聪明的你在努力成为一位 OIerOIerOIer 的过程中,一定会遇到这些奇奇怪怪的名词。虽然看起来不知道是什么意思,但理解了就很容易了。

机器数(Computer Number)

机器数一般分为原码反码补码三种,是为机器存储数据而设计的特殊二进制代码。

这篇文章说的都是有符号的整数,最左端的一位是符号位。以8位整数举例。

这里先简单说一下二进制吧。

二进制(Binary)

为了方便计算机存储数据,科学家发明了二进制(Binary),它只需要01就能表示任何整数。二进制中的一个01就被称为“位”(Binary digit,简称"Bit")

具体的转换方法:

  • 十进制转二进制:每次取模2获得余数,存在数组里再把要转换的数除以2。最后反过来就是它的二进制了。
// #include <vector>
// #include <algorithm>
std::vector<bool> dec_to_bin(int k)
{
    if (k == 0)
    {
        return std::vector<bool>(1, 0);
    }
    std::vector<bool> res;
    while (k)
    {
        res.push_back(k % 2);
        k /= 2;
    }
    std::reverse(res.begin(), res.end());
    return res;
}
  • 二进制转十进制:按权展开法,倒序遍历,权从 1 开始,如果这位是 1 ,就加上权,每次循环结束后都把权乘 2 。
// #include <vector>
int bin_to_dec(std::vector<bool> b)
{
    int p = 1;
    int res = 0;
    for (auto it = b.rbegin(); it != b.rend(); it++)
    {
        if (*it == 1)
        {
            res += p;
        }
        p *= 2;
    }
    return res;
}

原码(Sign-Magnitude)

正数的原码就是其二进制,若不足8位则补0
负数的原码需要在其二进制上将最左端的符号位设为1,表示这是负数。

例:

 10 转为二进制:
 0 0 0 0 1 0 1 0
 所以 108 位原码是:
 0 0 0 0 1 0 1 0

 -10 是负数,需要将原数的符号位设为 1。
 所以 -108 位原码是:
 1 0 0 0 1 0 1 0
 ^
最左边的这个就是符号位
原码的优点

容易理解,最接近普通二进制,转换方便。

原码的缺点

对于电脑来说,原码的负数就不太好理解了。

反码(Ones’ Complement)

正数的反码与原码相同。
负数的反码是对原码除符号位外按位取反。

例:

 10 的原码是:
 0 0 0 0 1 0 1 0
 正数反码与原码相同,所以 10 的反码是:
 0 0 0 0 1 0 1 0

 -10 的原码是
 1 0 0 0 1 0 1 0
 负数补码需要按位取反,所以 -10 的反码是:
 1 1 1 1 0 1 0 1
反码的优点

对于机器来说,反码让减法和负数的实现更简单了。

反码的缺点

对于人来说,负数的反码转换时会难一些。

补码(Two’s Complement)

正数的补码与原码相同。
负数的补码是他的原码按位取反(除符号位)后 + 1 (反之亦然)。

例:

 10 的原码是:
 0 0 0 0 1 0 1 0 
 正数补码与原码相同,所以 10 的补码是:
 0 0 0 0 1 0 1 0

 -10 的原码是:
 1 0 0 0 1 0 1 0
 负数补码是其原码按位取反后 + 1,所以是
 1 1 1 1 0 1 0 1 + 1
=1 1 1 1 0 1 1 0
补码的优点

计算机使用的就是补码,因为它对于计算机的运算是最便捷的,补码可以将减法转换为加法。
例:a + b = a + (~b + 1) ,其中~表示按位取反,这样不用判断ab大小关系,优化了算法。自己多试一试就能发现它的好处。

补码的缺点

对于人来说,负数的补码转换时会难一些,因为这需要进行两步操作。


位运算(Bitwise Operation)

位运算和机器数有着很大的关联。二进制数据中的一个01就被称为“位”。

相比于四则运算,位运算会快很多,因为它的操作更接近计算机底层。

需要注意的是,位运算的优先级都很低,所以尽量加括号,不然可能报错。

左移、右移(<<, >>)

顾名思义,他们会对原数的补码左移或右移,相当于*2/2,但速度更快。
左移过多可能导致溢出,右移不会改动符号位。

格式:

int a = 1;
a <<= 5; // a左移5位,现在应该是32
a >>= 3; // a右移3位,现在应该是4

按位与(AND)、或(OR、非(NOT)(&, |, !)

和逻辑上的差不多,但会对每一位进行操作。

  • 与:当两个都是 1 时则输出 1。
  • 或:当其中存在 1 时则输出 1.
  • 非:1 变 0 , 0 变 1。

按位与和按位或是双目运算符,按位非(按位取反)是单目运算符。

注:这三个操作会影响符号位。比如:一个整数按位取反以后会变成负数。

格式:

int _or = (10 | 7); 
//     10 = 1 0 1 0 (2)
//      7 = 0 1 1 1 (2)
// 10 | 5 = 1 1 1 1 (2) = 15 
// 所以 _or 现在是 15 
int _and = (10 | 7); 
//     10 = 1 0 1 0 (2)
//      7 = 0 1 1 1 (2)
// 10 & 5 = 0 0 1 0 (2) = 2
// 所以 _and 现在是 2
short _not = (~10);
//  0000 0000 0000 1010 (2)
// =1111 1111 1111 0101 (2)
// =-11 (10)
// 所以现在 _not 是 -11,前文说过怎么把负数(补码)转为正数。

按位异或(^,XOR)

当两位不同时则输出 1 ,相同则输出 0 。
这是一个比较特殊的运算符,因为它有很多神奇的特点。

异或的几个性质:

  • (a ^ b) ^ c = a ^ (b ^ c) 符合结合律,可以按任意顺序异或
  • x ^ x=0,x ^ 0 = x 一个数异或自己等于 0,异或 0 等于本身
  • a ^ b ^ b = a ^ 0 = a 有偶数个相同的数取异或相当于异或0(推论)

格式:

int _xor = (10 ^ 7);
//     10 = 1 0 1 0 (2)
//      7 = 0 1 1 1 (2)
// 10 ^ 7 = 1 1 0 1 (2) = 13
// 所以 _xor 现在是 13

因为异或的各种特性,有很多有用函数利用了异或,优化了执行效率。

如:

void _swap(int &x, int &y) // 交换两数
{
    x = x ^ y;
    y = x ^ y;
    x = x ^ y;
}
int lowbit(int x) // 获取一个数在二进制中的最低位
{
    return x & -x;
}

按位同或(XNOR)

和异或相反,相同则输出 1 ,不同则输出 0 。

虽然 C++ 中没有同或运算符,但是由于它和异或正好相反,所以可以使用!(a ^ b)来实现同或。


以上就是 C++ 中的位运算。运用位运算,可以优化前文提到的二进制转换代码:

// #include <vector>
// #include <algorithm>
std::vector<bool> dec_to_bin(int k)
{
    if (k == 0)
    {
        return std::vector<bool>(1, 0);
    }
    std::vector<bool> res;
    while (k)
    {
        res.push_back(k & 1);
        k >>= 1;
    }
    std::reverse(res.begin(), res.end());
    return res;
}
int bin_to_dec(std::vector<bool> b)
{
    int p = 1;
    int res = 0;
    for (auto it = b.rbegin(); it != b.rend(); it++)
    {
        if (*it == 1)
        {
            res += p;
        }
        p <<= 1;
    }
    return res;
}

以及一些函数:

int getbit(int x, int pos) // 获取某一位
{
    return ((x >> pos) & 1);
}

int setbit(int x, int pos, int to)
{
    if (getbit(x, pos) == to)
    {
        return x;
    }
    return (x ^ (1 << pos));
}

int _abs(int x) // 绝对值
{  
    return (x ^ (x >> 31)) - (x >> 31); // long long 就把 31 改成 63
} 
本文就到这里了,谢谢观看!

Bye!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值