位移操作
- 二进制优势在于容易表示、抗干扰等,在表示模拟信号的时候也有优势。
- 运算符
- &, |, !
- &&, ||, !!
>>
,<<
:位移运算又分为逻辑位移、算术位移, 其中理解算数右移需要理解计算机内如何表示负数.
位移实验
移动的位数等于int的位数(4bytes * 8 = 32bis),结果不变。
如果是31位的话:
书中的描述:在许多机器上,当移动一个 \(w\) 位的值时,移位指令只考虑位移量的低 \(log_2w\) 位,因此实际上位移量是通过计算\(k\;mod\;w\)得到的,这里的\(k\)是程序写的位移量。所以位移32位,32 mod 32 = 0,31 mod 32 = 31,因此前者相当于没位移(结果不变,是1012),后者位移了31位。
这个思路下,如果\(x = 1\),也就是\(00....1\),位移31位后,是\(100...0\),那它应该是\(MiniamlInteger\)的值。
\(B2U(Unsigned)\) 和 \(B2T(Two's complement)\) 有专门的计算公式. 前者是无符号数, 后者是补码.
二进制转无符号数是把对应比特位的2的幂累加.
\[B2U(X) = \sum_{i=0}^{w-1}x_{i}\cdot2^{ i} \]转补码 我们学到的是正数的补码就是其本身, 负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1).
\[B2T(X) = -x_{w-1}\cdot2^{w-1}+\sum_{i=0}^{w-2}x_{i}\cdot2^{i} \]理解了之后, 可以知道 \(Umin = 0, UMax = 2^w - 1, TMin = -2^{w-1}, TMax = 2^{w-1} - 1\). 其中补码的 \(w-1\) 可以理解成减去了最高位符号位.
此外, 从公式上也可以观察到, $ |TMin| = TMax + 1, UMax = 2 * TMax + 1$.
\(T\) 的-1, 以及 \(UMax\) 是 \(111...1\). 从公式上或者之前学到的都可以理解.
在做强制转换(Casting)的时候要注意这些特性, 比如 \(-1 > 0U\) , 这是因为无符号数的转换导致了-1变成了 \(UMax\). 混合计算的默认情况下有符号数会被隐式转换成无符号数.
所以要知道, \(-1 > -2, -1U > -2\) , 两者的底层逻辑是不一样的.
又比如 \(2147483647U < -2147483647-1\), 这时候前者是 \(TMax = 01...1\), 后者是 \(TMin = 100..0\), 都是无符号数的情况下, 两者差1.
#include <iostream>
#include <climits>
using namespace std;
int main() {
unsigned int i = INT32_MAX; // 2147483647U
int j = INT32_MIN; // -2147483647-1 = -2147483648
unsigned k = j;
cout << "i: " << i << endl;
cout << "j: " << j << endl;
cout << "k: " << k << endl;
bool res = i < j;
cout << "res: " << res << endl;
return 0;
}
// i: 2147483647
// j: -2147483648
// k: 2147483648
// res: 1
\(TMin\) 用 \(-TMax -1\) 的写法是因为 \(TMin = 100..0, TMax = 01...1\), 在编程中,特别是处理整数范围时,我们有时需要定义一个最小值和最大值。例如,在处理有符号整数(signed integer)时,最大值(通常称为
TMax
)和最小值(通常称为TMin
)是非常常见的定义。通常,这些值在表示整数的比特位数上有特定的关系。以32位有符号整数为例:
- 最大值(
TMax
):因为最高位是符号位,正数的最大值是\(2^{31} - 1\),即2147483647。- 最小值(
TMin
):最小值是负数,且比最大值多一个单位,因为0在正数的范围内。最小值是\(-2^{31}\),即-2147483648。在这种情况下,最小值
TMin
可以用公式表示:$ TMin = -TMax - 1 $这背后的原因是:
- 数值对称性:有符号整数范围是对称的,负数部分比正数部分多一个单位(因为包括0)。因此,负数的范围从\(-2^{31}\)到-1,而正数的范围从0到\(2^{31} - 1\)。
- 比特表示:有符号整数在计算机内部是用补码表示的。补码表示的特点使得最小负数比最大正数多一个单位。
通过这种表示方法,可以简化整数范围的定义和操作,也有助于在编写代码时避免溢出错误。简单来说,\(TMin = -TMax - 1\)这一写法是对有符号整数范围的一种精确定义,确保了范围的对称性和正确性。
转换 (Cast)
符号扩展 (Sing Expand)
比如8位数字扩展到16位数字, 如何去在不改变实际值的情况下扩展?
正数的情况, 符号位是0, 往前扩展0.
负数的情况, 符号位是1, 往前扩展1. 比如 \(1110\), 此时可以理解成 \(-8+4+2=-2\), 用取反+1也可以算. 扩展后变成 \(11110\), 此时是 \(-16+8+4+2=-2\) , 可以看到 \(-16+8=-8\), 新权重和旧权重加起来, 总和依然不变. 这也是二进制的特性.
总结一下就是无符号数扩展0, 有符号数扩展符号位.
截断 (Truncate)
基本是直接截断位数. 截断会不可避免地导致数据丢失, 所以要尽量避免.
无符号位的情况, 比如 \(11011 = 27, 1011 = 11\), 也可以说是 \(mod16\).
有符号位的情况, 比如 \(11011 = -5, 1011 = -5\). 这是Sign Expand带来的错觉, 需要理解成 \(mod 16\).
或者说 \(10011 = -13, 0011 = 3\), 这是 $ (16+2+1)mod16 = 3$.
在计算机中,有符号整数的表示方式通常使用补码(Two's Complement)。补码的一个重要特性是,可以用统一的方式表示正数和负数,并且可以直接使用二进制加法器来处理加减运算。
对于一个二进制数,如果我们将其截断到更少的位数,会影响它的表示和值。以下是详细解释:
补码表示法
首先,理解补码的基本概念。对于一个n位的二进制数:
- 正数的补码表示与其原码(普通二进制表示)相同。
- 负数的补码表示是将该数的绝对值按位取反,然后加1。
示例分析
假设我们有一个5位的二进制数 10011
,以及一个4位的二进制数 0011
。我们来逐步分析它们的补码表示和截断后的影响。
1. 原数 10011
(5位)
- 这个数是一个5位的二进制数,最高位是1,表示这是一个负数。
- 计算其补码:
- 取反:
10011
->01100
- 加1:
01100
+1
=01101
- 所以,
10011
的绝对值是01101
(即13),原数是-13
。
- 取反:
2. 截断到4位:0011
- 截断后只保留低4位:
0011
0011
是一个4位的二进制数,最高位是0,表示这是一个正数。- 直接计算值:
0011
的值是3
。
截断导致的问题
截断的过程中,最左边的一位被舍弃,这改变了数值的符号和大小。在补码表示法中,最高位(符号位)的变化会直接影响数值的正负,因此截断后的结果可能与原来的数值有很大的差异。
总结起来,10011
是一个5位的负数,截断为 0011
后变成了4位的正数,这种截断操作破坏了原有的补码结构,导致负数 -13
变成了正数 3
。
图示
更直观的解释可以通过图示:
原数: 10011 (5位,补码表示 -13)
截断: 0011 (4位,补码表示 3)
因此,截断操作改变了符号位和数值位,导致 -13
变成 3
。