文章目录
Abstract
- 机器数@移位操作@二进制移位运算@机器数位扩充@同一个真值三种机器数结构上的关系@算数移位及其移位后的空位添补规则
定点数位数扩充(补齐)
- 有时候,给定真值的二进制表示位数较短,为了对齐机器字长(寄存器字长),需要扩充位数
- 扩充后的机器码所对应的真值与扩充前的真值相等
- 主要讨论3种机器码的扩充问题
- 原码 ( T ( x ) ) (T(x)) (T(x))
- 补码 ( O ( x ) ) (O(x)) (O(x))
- 反码 ( A ( x ) ) (A(x)) (A(x))
正数的机器码扩充
- 正数的机器码扩充比较容易,因为T(x)=O(x)=A(x)
- 它们都是在边缘补0
- 整数在高位补0
- 小数在低位补0
负数的机器码扩充
- 不同的机器码具有
- 共同点:
- 符号位不变
- 不同点:
- 补位数码规律不同
- 共同点:
- 下面这些规律,先从原码入手,补原码的时候总是补0,而反码是原码数值位取反,对应位置总是补1,补码是反码+1得到的,反码高位部分没有因为+1收到影响,因此高位补的和反码一样都是1
- 总之补齐字长的情况下,只可能往高位补,根源是因为直观的原码就是高位补齐(对于整数);对于小数,也从原码补齐出发,推出反码,补码是从低位补位
原码
- 符号保持不变
- 整数扩充:高位补0
- 小数扩充:低位补0
反码
- 符号保持不变
- 整数扩充:高位补1
- 小数扩充:低位补1
补码
- 符号保持不变
- 整数扩充:高位补1(也就是符号位)
- 小数扩充:低位补0
定点数的移位运算
r r r进制数移动小数点
-
十进制数中,乘以10相当于把小数点往右移动1位,除以10相当于把小数点往有移动1位
-
事实上,不仅十进制如此,其他进制也是如此,比如二进制
-
但是考虑到计算机中的定点数表示,计算机中小数点的位置是事先约定的
-
因此机器数中没有移动小数点的操作,而是有一种效果类似的移位操作
定点数的移位运算
-
当计算机中没有乘除法运算电路时,可以通过加法和移位相结合的方法来实现乘/除法运算。
-
计算机中小数点的位置是事先约定的
-
因此,二进制表示的机器数 x = x 0 x 1 ⋯ x n x=x_{0}x_{1}\cdots{x_{n}} x=x0x1⋯xn在相对于小数点作 t t t位左移或右移时,其实质就是该数乘以或除以 2 t 2^{t} 2t
-
x = ∑ i = 0 n x i 2 i x=\sum\limits_{i=0}^{n}x_i2^i x=i=0∑nxi2i
-
左移一位(如果不溢出)相当于乘以2
-
右移一位(如果不考虑被因移位而被舍去的末尾位),相当于除以2
-
-
-
对于任意二进制整数,左移一位,若不产生溢出,相当于乘以2(与十进制数的左移一位相当于乘以10类似):右移一位,若不考虑因移出而舍去的末位尾数,相当于除以2。
-
根据操作数的类型不同,位运算可以分为逻辑移位和算术移位。
逻辑移位
- 逻辑移位将操作数视为无符号整数。
- 逻辑移位的规则:左移时,高位移出,低位补0;右移时,低位移出,高位补0。
- 对于无符号整数的逻辑左移,若高位的1移出,则发生溢出。
算术移位和移位丢位问题👺
- 算术移位需要考虑符号位的问题,即将操作数视为有符号整数。
- 计算机中的有符号整数都是用补码表示的,因此对于有符号整数的移位操作应采用补码算术移位方式。
- 算术移位的规则:
- 左移时,高位移出,空位出现在低位,对低位补0,
- 若移出的高位不同于移位后的符号位,即左移前后的符号位不同,则发生溢出:
- 右移时,低位移出,高位补符号位,(符号位是0就补0,是1就补1)
- 若低位的1移出,则影响精度。(补码的低位(至少1位)和其原码相同,原码低位丢1影响精度,补码同样如此)
- 左移时,高位移出,空位出现在低位,对低位补0,
- 例如,补码1001和0101左移时会发生溢出,右移时会丢失精度。
- 1001左移丢失符号位1,负数变成正数,发生溢出错误,0101左移则是符号位从0变为1,发生溢出错误
- 1001,0101右移都会丢失1,造成精度丢失
小结
-
有符号数的移位称为算术移位,无符号数的移位称为逻辑移位
-
算数移位三种机器数(原码,反码,补码)移位后符号位均不变,而无符号数的移位(逻辑移位)没有符号位,无论左移还是右移,移位产生的空位一律补0
-
为了避免算数左移时最高位丢1造成的错误,可以采用带进位 C y C_{y} Cy的移位,算数左移时,符号位移至 C y C_{y} Cy,最高位可以避免移丢.
单符号位定点小数的移位操作
算术移位规则
- 对于正数,由于 [ x ] 原 = [ x ] 补 = [ x ] 反 = 真值 [x]_{原} = [x]_补 = [x]_反 = \text{真值} [x]原=[x]补=[x]反=真值 ,故移位后出现的空位均以 0 添之。
- 对于负数,由于原码、补码和反码的表示形式不同,故当机器数移位时,对其空位的添补规则也不同。
- 移位操作的对象从真值 x x x入手,左移一位, ∣ x ∣ |x| ∣x∣乘以2,右移1位, ∣ x ∣ |x| ∣x∣除以2,总结规律后,我们可以直接对机器数进行移位,并且能够知道空出来的位应该填充什么
- 下表列出了三种不同码制的机器数(整数或小数均可),分别对应正数或负数移位后的添补规则。
不同码制机器数算术移位后的空位添补规则
真值 | 码 制 | 添补代码 |
---|---|---|
正数 | 原码、补码、反码 | 0 |
负数(需要分被讨论原码,反码,补码) | 原 码 | 0 |
反 码 | 1 | |
补 码 | 左移添 0; 右移添 1 |
容易混淆的两点
- 负数的补码左移,低位补0(空位产生于低位)
- 负数的补码右移,高位补1(空位产生于高位)
移位特点
符号位不受移位操作的影响:不论是正数还是负数,移位后其符号位均不变,这是算术移位的重要特点。
- 机器数为正时,不论是左移还是右移,添补代码均为 0。
- 由于负数的原码数值部分与真值相同,故在移位时只要使符号位不变,其余空位均添 0 即可。
- 由于负数的反码各位(除符号位外)与负数的原码正好相反,故移位后所添的代码应与原码所添加的0相反,即全部添 1。
- 分析任意负数的补码
C
(
x
)
=
x
0
,
x
1
⋯
x
t
⋯
x
n
C(x)=x_{0},x_{1}\cdots{x_{t}}\cdots{x_{n}}
C(x)=x0,x1⋯xt⋯xn,设
x
t
=
1
x_{t}=1
xt=1是从低位到高位找到的第一个
1
1
1;又设
A
(
x
)
=
x
0
,
y
1
⋯
y
t
⋯
y
n
A(x)=x_{0},y_{1}\cdots{y_{t}}\cdots{y_{n}}
A(x)=x0,y1⋯yt⋯yn是
x
x
x的反码
- 可发现,当对其(二进制串)进行由低位向高位找到第一个“1”时(设其为
x
t
x_{t}
xt)
- 在此“1”左边的各位 x 1 ⋯ x t − 1 x_{1}\cdots{x_{t-1}} x1⋯xt−1均与对应的反码相同,
- 在此“1”右边的各位(包括此“1”在内,即 x t ⋯ x n x_{t}\cdots{x_{n}} xt⋯xn)均与对应的原码相同(或者说与对应反码相反)。
- 事实上这很好理解,补码是反码+1得到的, y n + 1 y_{n}+1 yn+1无非是0或1,如果结果是0,说明发生了进位,这种情况下又要考虑 y n − 1 + 1 y_{n-1}+1 yn−1+1是否还会发生进位,往高位方向,最后一位发生变换的位设为 y t y_{t} yt,即 y t − 1 y_{t-1} yt−1以及更高位的位不会发生变换;这就是说, x 1 ⋯ x t − 1 = y 1 ⋯ y t − 1 x_{1}\cdots{x_{t-1}}=y_{1}\cdots{y_{t-1}} x1⋯xt−1=y1⋯yt−1,而 x t ⋯ x n x_{t}\cdots{x_{n}} xt⋯xn= y ‾ t ⋯ y ‾ n \overline y_{t}\cdots\overline y_{n} yt⋯yn
- 这说明,补码移动后空位补的数码要分两种情况,如果是空位发生在低位,则补充的数码和反码所需要补充的数码(1)相反,即补0;反之如果空位发生在高位,则补充的数码和反码需要补充的数码(1)相同,也是补1;
- 这个规律也告诉我们,不仅从反码到补码只需要加1,从补码到反码不仅可以减1,还可以从低位到高位做取反,直到第一个1被取反为0结束
- 例如 C ( − 2 ) = 1 , 110 C(-2)=1,110 C(−2)=1,110,则 A ( − 2 ) = 1 , 101 A(-2)=1,101 A(−2)=1,101
- 故负数的补码左移时,因空位出现在低位,则添补的代码与原码相同,即添 0
- 故负数的补码右移时因空位出现在高位,则添补的代码应与反码相同,即添 1
- 可发现,当对其(二进制串)进行由低位向高位找到第一个“1”时(设其为
x
t
x_{t}
xt)
小结
-
上面的结论最重要的是指出规律的普遍性,那么我就可以观察个例来还原或回想起来左右移位后空位究竟该补什么,尤其是负数补码
-
例如,对于字长为2,即1位符号位,1位数值位的情况来说, C ( − 1 ) = 1 , 1 C(-1)=1,1 C(−1)=1,1, C ( − 2 ) = 1 , 0 C(-2)=1,0 C(−2)=1,0
-
− 1 -1 −1左移得到 − 2 -2 −2,空位出现在低位,补0;反之 − 2 -2 −2右移得到 − 1 -1 −1,空位出现高位,补1
-
x = [-2]; 0b10; 0xfffe x = [-1]; 0b11; 0xffff
-
机器字长位4位的情况下, − 2 , − 1 -2,-1 −2,−1的补码情况如下
x = [ -2]; 0b1110; 0xfffe x = [ -1]; 0b1111; 0xffff
-
C++内建移位运算符
对于内建的移位运算符,两个操作数都必须具有整数或无作用域枚举类型,并且会对它们进行整数提升。
在本段的后续描述中,“操作数”、a、b、左操作数 和右操作数 均指代提升后的操作数。
任何情况下,如果右操作数 的值为负或不小于左操作数 中的位数,那么行为未定义。
老标准(C++20前)
-
左移
-
对于无符号a, a < < b a << b a<<b的值是 a × 2 b m o d 2 N a \times 2^b \mod 2^N a×2bmod2N,其中N是返回类型中的位数(即进行逐位左移并舍弃移出目标类型的位)。
-
对于有符号的非负a,如果 a × 2 b a \times 2^b a×2b能以返回类型的无符号版本表示,那么将改值转换到有符号后即是 a < < b a << b a<<b的值(这使得以 1 < < 31 1 << 31 1<<31创建 I N T _ M I N INT\_MIN INT_MIN合法);否则行为未定义。
-
对于负a, a < < b a << b a<<b的行为未定义。
-
-
右移
-
对于无符号a和有符号的非负a, a > > b a >> b a>>b的值是 ⌊ a 2 b ⌋ \left\lfloor \frac{a}{2^b} \right\rfloor ⌊2ba⌋的整数部分。
-
对于负a, a > > b a >> b a>>b的值由实现定义(大多数平台上进行算术右移,因此结果保留为负)。
-
C++20后
-
a
<
<
b
a << b
a<<b的值是与
a
×
2
b
a \times 2^b
a×2b对
2
N
2^N
2N同余的唯一值,其中N是返回类型中的位数
- 即进行逐位左移并舍弃移出目标类型的位
- 即有符号 a a a的左移是逻辑左移
- a > > b a >> b a>>b的值是 a a a除以 2 b 2^b 2b向负无穷取整(换言之,有符号a上的右移是算术右移)。
返回类型是左操作数 的类型。
移位示例代码C++
#include <bits/stdc++.h>
/*
演示负数补码移位操作,空位填充
向左移动数值绝对值变大,空位出现在低位,和原码一样填补0
向右移动数值绝对值变小,空位出现在高位,和反码一样填补1
*/
using namespace std;
int main(int argc, char const *argv[])
{
int a = -2;
// a << 1;
// int c = 0x8fffffff;
int c = 0x80000000;
int c1 = c << 1;
int c2 = c >> 1;
int const bits = 4;
bitset<bits> b(a);
bitset<bits> b1(a >> 1);
bitset<bits> b2(a << 1);
cout << (a << 1) << ';' << "0b" << b2 << endl
<< a << ';' << "0b" << b << endl
<< (a >> 1) << ';' << "0b" << b1 << endl;
cout << "c=" << dec << c << ";" << showbase << hex << c << endl
<< "(c<<1)=" << dec << c1 << ";" << showbase << hex << c1 << endl
<< "(c>>1)=" << dec << c2 << ";" << showbase << hex << c2 << endl;
return 0;
}
-4;0b1100
-2;0b1110
-1;0b1111
c=-2147483648;0x80000000
(c<<1)=0;0
(c>>1)=-1073741824;0xc0000000
移位造成的位丢失👺
对于正数,三种机器数移位后符号位均不变,左移时最高数位丢1,结果出错:右移时最低数位丢1,影响精度。也就是说,正数的三种机器数移位丢1时才会对结果或精度产生影响
对于负数,三种机器数算术移位后符号位均不变。
会造成结果出错或精度损失的情况:
- 负数的原码左移时,高位丢1,结果出错:右移时,低位丢1,影响精度(原码移位丢1有影响)
- 负数的反码左移时,高位丢0,结果出错:右移时,低位丢0,影响精度(反码移位丢0有影响)
- 负数的补码左移时,高位丢0,结果出错;右移时,低位丢1,影响精度(是反码和原码的折衷)