CSAPP读书笔记--信息的表示和使用(一)

本文是CSAPP读书笔记的第一部分,主要探讨信息的表示和使用,特别是整型数据的表示。内容涵盖字节、字、数据长度、字节序、运算(位级、逻辑、移位)以及整型数据(无符号数和有符号数的编码、转换)。文章详细解释了补码、无符号数的转换规则,并讨论了不同字长类型转换的零扩展和截断位操作,以及整数运算如加法和乘法的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CSAPP读书笔记–信息的表示和使用(一)

  CSAPP的part 1主要介绍程序的结构和执行,而第二章作为PART 1的第一个章节,主要讲述的是信息的表示和使用,这部分的主要内容包括:
在这里插入图片描述

  本文主要讲一下整型数据的部分,下一篇文档继续介绍浮点型数据。

信息存储

字节(Byte)

  信息在内存中的存储并不是以单个bit为单位的,而是以8bit的块为单位,这个8bit的块,就称为字节(Byte)。因此,字节也是内存寻址的最小单位。通常我们在表示byte数据的时候使用16进制, 也就是我们用16进制来写bit模式。
  操作系统给每个进程提供了虚拟内存的抽象,让进程都能访问从相同地址开始的、连续的虚拟内存空间,每个内存单位都有唯一的编码进行标识,这个编码称为地址(Address),而所有地址的集合就构成了虚拟内存空间。那虚拟内存空间究竟有多大是由什么来决定的呢?这主要取决于计算机的一个参数——字长(Word Size)。什么是字长?什么又是字呢?如下。

字(word)

  我们可以将若干个字节当做一个块,称为字(Word),而这里的字节的数目就是字长。字长指明了指针数据的标称大小(Nominal Size),而指针指向虚拟内存空间,它的位数就决定了它能索引多大的空间,由此也就规定了虚拟内存空间的最大大小。所以虚拟空间的最大大小由字长决定。
  当前大部分计算机的字长都是32bit或64bit,而程序可以通过不同的编译指令将其编译成32位程序或者64位程序(程序的字长是由编译决定的),其中32位机器可以运行32位程序,但是不能运行64位程序,而64位机器可以运行32位程序和64位程序,即64位机器是兼容32位程序的。

数据长度

  对于不同的数据类型,它实际占用的字节数,也跟编译器将程序编译成32位还是64位有关。C语言中常见数据类型所占字节数在32位程序和64位程序中的对比如下:

在这里插入图片描述

字节序

  当存储多字节的目标程序或数据时,通常需要考虑两点:① 目标的地址是什么?② 应该怎样在内存中去排列各字节的数据。目标的地址指的就是字节序列的最低地址,而对于字节排列顺序,通常有两种方式:大端模式(big endian)和小端模式(little endian)。其中,大端模式指的是将较高位的有效字节存储在较低地址的内存中,而小端模式则是将较高位的有效字节存储在较高的内存地址中。举个例子,若现在需要将数据0x01234567,存储在内存地址0x100-0x103中,采用大端模式和小端模式的存储方式如下:

在这里插入图片描述

  不同的机器支持不同的字节序,如多数Intel的机器只支持小端模式,相反,IBM、Oracle的多数机器则只支持大端模式。此外,也有如ARM的微处理器支持两种字节序,可通过配置决定采用哪种字节序。通常来讲,一旦选定了操作系统,字节顺序也就固定了。如arm的处理器,虽然支持双端模式,但在上面使用安卓或IOS系统,就只能支持小端模式。
  一般而言,机器所使用的的字节顺序是不可见的,但是在有些情况下需要注意字节顺序带来的影响:

  • 在两个不同类型的机器之间通过网络传输数据时,如果这两个机器使用了不同的字节顺序,就会造成问题。所以网络应用程序的代码编写必须遵守已建立的关于字节顺序的规则, 确保发送方机器将它的内部表示转换成网络标准,而接收方机器将网络标准转换为它自己的内部表示。
  • 当我们通过反汇编器得到可执行程序的指令序列时,字节顺序也很重要。

运算

位级运算

  C中的整型数据类型是使用二进制数进行编码的,二进制数可以对应为布尔型的位向量,所以它可以支持按位的布尔运算,比如|表示OR、&表示AND、~表示NOT、^表示XOR。一个常见的应用举例:

//如flags代表某个feature支持的所有属性,它的每个bit对应某个特定的flag,每个bit对应flag定义如下:
#define flag_XX1 0x1
#define flag_XX2 0x2
#define flag_XX3 0x4
...
#define flag_XX4 0x80

//若想判断某个flag是否被置上,比如flag_XX3
if (flags & flag_XX3){
    //content
}

//若要置上某个标志位,比如flag_XX3
flags = flags | flag_XX3;

//若要清除某个flag, 如flag_XX3  
flags = flags & (~flag_XX3);

  此外,位级运算的另一个常见用法是进行严密运算,比如对int型数据a,提取最低有效字节为a&0xFF,保留除了最低有效字节以外的字节为a&~0xFF。

逻辑运算

  C提供了一组逻辑运算符||、&&和!分别对应于命题逻辑中的OR、AND和NOT运算。要注意逻辑运算和位级运算的区别,在逻辑运算中,只要是非零的数据就表示为TRUE,全零的数据就表示为FALSE,所以计算时候先将其转换为TRUE和FALSE,然后计算出来的结果只会是0x00或0x01,分别对应FALSE和TRUE。
  逻辑运算中一个值得关注的点,叫做early termination。它主要针对逻辑与运算和或运算。对于与运算,“一假则假”,如a&&b, 其中a、b分别为两个需要计算的表达式,若计算出a为flase, 则a&&b必为flase,b表达式将不会被执行和计算;同理,对于或运算,“一真则真”,如a||b, 若a计算结果为true,则a||b必为true,b将不会被执行。

移位运算

  C语言支持移位运算,具体分为左移和右移。

  • 左移:整型数x执行左移k位,即x << k,执行过程是,x按bit左移k位,最高的k位会被丢弃,然后在最右边补上k个0。
  • 右移:右移包括逻辑右移和算术右移。对x >> k,两种右移的共同点是,将整型数x按bit右移k位,并丢弃最低的k位,但是高位补齐方式不同。逻辑右移是直接用0进行高位补齐,而算术右移则是用符号位对高位进行补齐,具体而言,对有符号数,最高位为0表示正数,则右移高位用0补齐,若最高位为1,则为负数,高位用1补齐.

整型数据的表示

  整型数据包括无符号型和有符号型,无符号型只能表示非负数,而有符号型则既可以表示非负数,也可以表示负数。

无符号数的编码

  假设一个无符号整数x为w bit, 我们可以将它表示为一个向量[xw−1,xw−2,…,x0]\left[x_{w-1}, x_{w-2}, \ldots, x_{0}\right][xw1,xw2,,x0],作为x的二进制表示。定义一个函数B2UwB 2 U_{\mathrm{w} }B2Uw(binary to unsigned)表示将二进制转换为无符号数,其函数定义为:
B2Uw(x)=∑i=0w−1xi2i(1) B2{U_{\rm{w} } }(x) = \sum\limits_{i = 0}^{w - 1} { {x_i}{2^i} } \tag{1} B2Uw(x)=i=0w1xi2i(1)
  根据公式可知,相当于二进制转为十进制:

  • 最小值UMinwUMi{n_w}UMinw:[0,0,…,0],即0;
  • 最大值UMaxwUMa{x_w}UMaxw:[1,1,…,1],即∑i=0w−12i=2w−1\sum_{i=0}^{w-1} 2^{i}=2^{w}-1i=0w12i=2w1

有符号数的编码

  有符号数有很多不同的编码方式,比如补码(two’s complement)、反码(one’s complement)和原码(sign-magnitude)。其中最常见的是补码编码,C语言标准没有要求要用何种形式的编码来表示有符号整数,但是几乎所有机器都会使用补码编码。
  同样的,我们定义一个函数( B 2 T_{w} )表示二进制转化为补码编码的有符号数,定义如下:
B2Tw(x)=−xw−12w−1+∑i=0w−2xi2i(2) B2{T_w}(x) = - {x_{w - 1} }{2^{w - 1} } + \sum\limits_{i = 0}^{w - 2} { {x_i}{2^i} } \tag{2} B2Tw(x)=xw12w1+i=0w2xi2i(2)
  如何理解这个公式呢?实际上,对于补码形式,最高位xw−1x_{w-1}xw1为符号位,且赋予符号位一个权重−2w−1-2^{w-1}2w1

  • 最小值TMinwTMi{n_w}TMinw:[1,0,…,0],结果为−2w−1-2^{w-1}2w1
  • 最大值TMaxwTMa{x_w}TMaxw:[0,1,…,1],结果为∑i=0w−22i=2w−1−1\sum_{i=0}^{w-2} 2^{i}=2^{w-1}-1i=0w22i=2w11
  • -1:[1,1,…,1],与UMaxwUMa{x_w}UMaxw的二进制相同。

  我们可以看到补码编码的范围是不对称的,且∣TMinw∣=∣TMaxw∣+1|TMi{n_w}| = |TMa{x_w}| + 1TMinw=TMaxw+1, 这是因为补码的编码的形式,通过设置符号位,使负数和非负数各占一半的编码范围,而非负数包含正数和0,因此负数比正数多一个。

补充:前面提到,有符号数也可以使用反码(one’s compliment)编码或原码(Sign magnitude)编码,关于反码和原码的定义如下:
反码:跟补码编码方式类似,只是符号位的权重变成了−(2w−1−1)-\left(2^{w-1}-1\right)(2w11),之所以叫反码,是因为对负数而言,除符号位以外的部分其实就是将负数绝对值的二进制编码取反。反码值的计算公式为:
B2Ow=−xw−1(2w−1−1)+∑i=0w−2xi2i(3) B 2 O_{w}=-x_{w-1}\left(2^{w-1}-1\right)+\sum_{i=0}^{w-2} x_{i} 2^{i}\tag{3} B2Ow=xw1(2w11)+i=0w2xi2i(3)
原码:最高位为符号位,其余位为绝对值的二进制编码。计算公式如下:
B2Sw=(−1)xw−1⋅∑i=0w−2xi2i(4) B 2 S_{w}=(-1)^{x_{w-1} } \cdot \sum_{i=0}^{w-2} x_{i} 2^{i}\tag{4} B2Sw=(1)xw1i=0w2xi2i(4)
原码和反码对于数字0,均有两种编码方式,这也许就是几乎所有机器都用补码表示有符号数的原因。

有符号数和无符号数之间的转换

  C语言允许在各种不同的数据类型之间做强制转换,它的具体实现要从位级角度来看,它保持每bit的值不变,只是改变了解释这些位的方式。

补码转换为无符号数

  对于补码而言,当有符号数为正数时,其补码与其对应的无符号数相同,当有符号数为负数时,区别就在于符号位,只需要将符号位转换为无符号数的最高位数值即可。设补码为x,其二进制序列为[xw−1,xw−2,...,x0][{x_{w - 1} },{x_{w - 2} },...,{x_0}][xw1,xw2,...,x0], 则无符号数的值为
$T2{U_w}(x) = {2^{w - 1} } + \sum\limits_{i = 0}^{w - 2} { {x_i}{2^i} } $
结合公式(2),可进一步得到:
T2Uw(x)=2w−1+∑i=0w−2xi2i=2w−1+x+2w−1=x+2wT2{U_w}(x) = {2^{w - 1} } + \sum\limits_{i = 0}^{w - 2} { {x_i}{2^i} = } {2^{w - 1} } + x + {2^{w - 1} } = x + {2^w}T2Uw(x)=2w1+i=0w2xi2i=2w1+x+2w1=x+2w
综合一下正负数的情况,可得到补码转换为无符号数的公式如下:
对于补码x, TMin⁡w≤x≤TMax⁡w\operatorname{TMin}_{w} \leq x \leq \operatorname{TMax}_{w}TMinwxTMaxw:
T2Uw(x)={x,x≥0x+2w,x<0(5) T 2 U_{w}(x)= \begin{cases}x, & x \geq 0 \\ x+2^{w,} & x<0\end{cases}\tag{5} T2Uw(x)={x,x+2w,x0x<0(5)
根据上面公式(5),我们可以得到如下更直观的图:
在这里插入图片描述

无符号数转换为补码

  无符号数的最高位为0时,其值跟补码相同,但最高位为1时,转换成补码就是一个负数,设无符号数为u, 其二进制表示为[uw−1,uw−2,...,u0]\left[ { {u_{w - 1} },{u_{w - 2} },...,{u_0} } \right][uw1,uw2,...,u0], 则补码的值为:U2Tw(u)=−2w−1+∑i=0w−22iuiU2{T_w}(u) = - {2^{^{w - 1} } } + \sum\limits_{i = 0}^{w - 2} { {2^i}{u_i} }U2Tw(u)=2w1+i=0w22iui
结合公式(1),可得:U2Tw(u)=−2w−1+∑i=0w−22iui=−2w−1+u−2w−1=u−2wU2{T_w}(u) = - {2^{^{w - 1} } } + \sum\limits_{i = 0}^{w - 2} { {2^i}{u_i} = - } {2^{^{w - 1} } } + u - {2^{^{w - 1} } } = u - {2^w}U2Tw(u)=2w1+i=0w22iui=2w1+u2w1=u2w
因此,无符号数转换为补码的公式如下:
U2Tw(u)={u,u≤TMaxwu−2w,u>TMaxw(6) U 2 T_{w}(u)= \begin{cases}u, & u \leq T M a x_{w} \\ u-2^{w}, & u>T M a x_{w}\end{cases}\tag{6} U2Tw(u)={u,u2w,uTMaxwu>TMaxw(6)
根据上面公式(6),我们可以得到如下更直观的图:

在这里插入图片描述

  C语言中数据做运算时,常常会出现隐式的数据转换。一个值得注意的场景就是,当一个无符号数和一个有符号数一起做运算时,有符号数会被隐式地转换为无符号数,再参与运算,而这样的隐式转换,往往是编程者容易忽略的,因此也经常是一个出问题的雷区。当出现逻辑运算时,常常会出现我们预想不到的情况,举例如下图:
在这里插入图片描述

由于有符号数到无符号数的隐式转换,可能会导致错误或漏洞,因此建议涉及运算时尽量不使用无符号数。不过,如果我们只是把字看成是位的集合,而没有实际意义,则无符号数非常有用。

不同字长的类型转换

  不同长度的数据类型转换,分为两种情况:1、从简短数据长度转换为较长的数据长度,比如从short转换为int,此时就需要扩展位;2、从较长数据长度转换为较短的数据长度,比如int转换为short,此时就需要截断位。

扩展位
  • 对无符号数而言,从较短长度的数据类型扩展到较长类型,只需要在高位补0就可以了,这种操作称为零扩展(zero extension);
  • 对有符号数(补码)而言,从较短长度的数据类型扩展到较长类型,则需要在高位补充最高有效位(即符号位),这种操作称为符号扩展(sign extension)。
  • 若除了需要扩展位以外,同时还要进行无符号和补码之间的转换,比如short转换为unsigned int,此时应该先扩展,还是先转换呢?答案是,根据C语言标准要求,先进行扩展位,再进行有符号和无符号转换
截断位
  • 对无符号数而言,较长长度的数据类型转换为较短类型,只需要将高位超出的部分丢弃即可。设转换前的数据为x, 二进制[xw−1,xw−2,…,x0]\left[x_{w-1}, x_{w-2}, \ldots, x_{0}\right][xw1,xw2,,x0],转换(截断)后的数据为x′x'x,二进制[xk−1,xk−2,…,x0]\left[x_{k-1}, x_{k-2}, \ldots, x_{0}\right][xk1,xk2,,x0]w>kw > kw>k。则x=B2Uw(x⃗)x=B 2 U_{w}(\vec{x})x=B2Uw(x), x′=B2Uw(x′⃗)x'=B 2 U_{w}(\vec{x'})x=B2Uw(x), 存在:x′=x mod 2kx' = x\bmod {2^k}x=xmod2k2k{2^k}2k取模,就相当于丢弃第k位到第w-1位,只保留最低k位。
  • 对补码而言,截断位的方式与无符号数相同,都是在二进制形式下丢弃高位超出的部分,只是最终的结果,需要转换为补码的值(把截断后的最高位作为符号位),用公式表示就是:x′=U2TK(x mod 2k)x' = U2{T_{_K}}(x\bmod {2^k})x=U2TK(xmod2k)

整数运算

无符号数加法

  两个w位的无符号数相加,当和的值大于等于2w{2^{\rm{w}}}2w时,其和的值就需要w+1位才能表示了,此时加法的结果就溢出了。而我们的结果要求依然是w位的话,其实就相当于将w+1位的无符号数转化为w位的无符号数,因此会产生位截断,即计算结果对2w{2^w}2w取模,由于只溢出1位,故取模就等同于减去2w{2^w}2w。因此,w位无符号数加法(用$ + _{\rm{w}}^{\rm{u}}$表示)的公式如下:

  若x,y满足0≤x,y<2w0 \leq x, y<2^{w}0x,y<2w,则
x+wuy={x+y,0≤x+y<2wx+y−2w,2w≤x+y<2w+1(7) x + _{\rm{w}}^{\rm{u}}y = \left\{ \begin{array}{l} x + y,0 \le x + y < {2^w}\\ x + y - {2^w},{2^w} \le x + y < {2^{w + 1}} \end{array} \right.\tag{7} x+wuy={x+y,0x+y<2wx+y2w,2wx+y<2w+1(7)
上述公式,可用如下图示更清晰的表达:

在这里插入图片描述

  由此,我们可以得到一个判断无符号数加法溢出的办法,
即对于x,y,满足0≤x,y≤0 \leq x, y \leq0x,y UMax w_{w}ws=x+wuys = x + _{\rm{w}}^{\rm{u}}ys=x+wuy,若s<xs<xs<x或者s<ys<ys<y,则说明x+wuyx + _{\rm{w}}^{\rm{u}}yx+wuy溢出了。

加法逆元:若x+wuy=0x + _{\rm{w}}^{\rm{u}}y=0x+wuy=0,则y为x的加法逆元。利用溢出效应,使两数相加刚好溢出,其结果即为0。则求加法逆元的公式如下:
−wux={0,x=02w−x,x>0(8)- {}_w^ux = \left\{ \begin{array}{l} 0,x = 0\\ {2^w} - x,x > 0 \end{array} \right.\tag{8} wux={0,x=02wx,x>0(8)

补码加法

  对于补码加法,本质上还是先做二进制加法,若溢出则截断,只是截断后的结果需要解释成补码,即x+wty=U2Tw(T2Uw(x)+wuT2Uw(y))x+_{w}^{t} y=U 2 T_{w}\left(T 2 U_{w}(x)+_{w}^{u} T 2 U_{w}(y)\right)x+wty=U2Tw(T2Uw(x)+wuT2Uw(y)),针对正溢出、负溢出和不溢出三种情况展开,公式如下:
  若x、y满足:−2w−1≤x,y≤2w−1−1-2^{w-1} \leq x, y \leq 2^{w-1}-12w1x,y2w11,则
x+wuy={x+y+2w,x+y<−2w−1x+y,−2w−1≤x+y<2w−1x+y−2w,x+y≥2w−1(9) x + {}_w^uy = \left\{ \begin{array}{l} x + y + {2^w},{\rm{ }}x + y < - {2^{w - 1}}\\ x + {\rm{y}}, - {2^{w - 1}} \le x + y < {2^{w - 1}}\\ x + y - {2^w},{\rm{ }}x + y \ge {2^{w - 1}}{\rm{ }} \end{array} \right.\tag{9} x+wuy=x+y+2w,x+y<2w1x+y,2w1x+y<2w1x+y2w,x+y2w1(9)
图示如下:
在这里插入图片描述

  如何判断补码溢出?对于x,y,若满足TMin⁡w≤x,y≤TMax⁡w\operatorname{TMin}_{w} \leq x, y \leq \operatorname{TMax}_{w}TMinwx,yTMaxws=x+wuys = x + _{\rm{w}}^{\rm{u}}ys=x+wuy

  • 正溢出:若x>0, y>0, 而s≤0s \leq 0s0, 则发生正溢出;
  • 负溢出:若x<0,y<0,而s≥0s \geq 0s0,则发生负溢出。

  对于补码的加法逆元,由于补码的范围为−2w−1≤x≤2w−1−1-2^{w-1} \leq x \leq 2^{w-1}-12w1x2w11,当x≠−2w−1x \ne - {2^{w - 1}}x=2w1时,取值范围是对称的,也就是说一定存在一个−x- xx也在这个范围内。而x=−2w−1x = - {2^{w - 1}}x=2w1, 则其加法逆元为−2w−1- {2^{w - 1}}2w1, 因为相加刚好负溢出,结果为0。因此,补码加法逆元的公式如下:
−wtx={TMin⁡w,x=TMin⁡w−x,x>TMin⁡w(10) -{ }_{w}^{t} x=\left\{\begin{array}{cc} \operatorname{TMin}_{w}, & x=\operatorname{TMin}_{w} \\ -x, & x>\operatorname{TMin}_{w} \end{array}\right.\tag{10} wtx={TMinw,x,x=TMinwx>TMinw(10)

无符号数乘法

  当两个w位的无符号数相乘时,最多需要2w位才能完整表示,若乘积仍然为w位,则产生位截断,只保留低w位,根据前面的介绍,这相当于是对2w2^{w}2w取模。公式如下:
对满足0≤x,y≤UMaxw0 \leq x, y \leq U M a x_{w}0x,yUMaxw的x,y,其乘积:
xw∗uy=(x⋅y) mod 2w(11) x_{w}^{* u} y=(x \cdot y) \bmod 2^{w}\tag{11} xwuy=(xy)mod2w(11)

补码的乘法

  对于补码的乘法,原理上与无符号数类似,只要按无符号数乘积的方式计算,然后再将结果转为补码。如下:
xw∗ty=U2Tw((x⋅y) mod 2w)(12) x_{w}^{* \mathrm{t}} y=U 2 T_{w}\left((x \cdot y) \bmod 2^{w}\right)\tag{12} xwty=U2Tw((xy)mod2w)(12)

定理:对于长度为w的两个位向量xˉ\bar xxˉ,yˉ\bar yyˉ, 使用补码编码对应的值为x和y, 使用无符号编码对应的值为x′x'xy′y'y, 则
T2Bw(x∗wty)=U2Bw(x′∗wty′)(13)T2{B_w}(x*_w^ty) = U2{B_w}(x'*_w^ty')\tag{13}T2Bw(xwty)=U2Bw(xwty)(13)
利用公式(5)补码转为无符号数,不难证明上面的结论。

与常数相乘

  在许多机器上,整数的乘法指令运行起来非常的慢,可能需要10个以上的时钟周期,而其他的整数运算如加法、减法、位级运算、移位等,可能只需要1个时钟周期。因此,编译器往往会将整数与一个常数的乘法优化成移位+加法的形式。
  当整数(无论是无符号数还是补码)左移的时候,左移k位就相当于乘以2k{2^k}2k。以这个结论为基础,当整数与一个无符号整数常数相乘时,只需要将这个常数表示成二进制形式,然后就可以转化成∑i=0w−1xi2i\sum\limits_{i = 0}^{w - 1} { {x_i}{2^i} }i=0w1xi2i的形式,
也就可以把乘法转化成移位运算和加法运算。以x∗14x*14x14为例,常数因子14的二进制表示为[1110],因此14=23+22+2114 = {2^3} + {2^2} + {2^1}14=23+22+21, 于是,
14∗x=23∗x+22∗x+21∗x=(x<<3)+(x<<2)+(x<<1)14*x = {2^3}*x + {2^2}*x + {2^1}*x = (x < < 3) + (x < < 2) + (x < < 1)14x=23x+22x+21x=(x<<3)+(x<<2)+(x<<1)
如此,乘法运算就被转化成了3次移位运算+2次加法运算。
  另外,上述例子,14的二进制的形式中,bit值为1的位是连续的,且最低的值为1的位后全为0,对于这样的形式,假设最高的值为1的位为第n位,最低的值为1的位位第m位,则该常数乘法结果也可以表示成2n+1−2m{2^{n + 1}} - {2^m}2n+12m, 而如上的例子,常数因子14可被表示为24−21{2^4} - {2^1}2421,这就变成了两次移位运算和一次减法运算。
  那么,计算机在实际执行这种乘法时,是转化为移位+加/减的形式,还是直接使用乘法,取决于这两种形式的相对运行速度。只有在转化为移位+加/减后真的能提高运算速度,才会优化,换句话说,只有转换的结果是比较少的移位和加/减法运算的时候,编译器才会优化。

除以2的n次方

  在大多数机器上除法运算比乘法运算还要慢,它一般需要30个以上时钟周期。而除以2n{2^n}2n可以通过移位运算来实现,除以2n{2^n}2n可以转化为右移n位(无符号数做逻辑右移,补码做算术右移)。但是与乘法运算不同的是,除法运算会存在除不尽的情况,此时就需要做舍入,我们的整数除法的舍入总是要求向0舍入的。
  假设对整数除以2k{2^k}2k,即右移k位。

  • 无符号数除以2k{2^k}2k相当于右移k位:
      假设无符号数x的二进制形式为[xw−1,xw−2,…,x0]\left[x_{w-1}, x_{w-2}, \ldots, x_{0}\right][xw1,xw2,,x0],设x′x'x[xw−1,xw−2,…,xk]\left[x_{w-1}, x_{w-2}, \ldots, x_{k}\right][xw1,xw2,,xk], x′′x''x[xk−1,xk−2,…,x0]\left[x_{k-1}, x_{k-2}, \ldots, x_{0}\right][xk1,xk2,,x0], 则x=x′∗2k+x′′x = x'*{2^k} + x''x=x2k+x,因此,x/2k=x′+x′′/2kx/{2^k} = x' + x''/{2^k}x/2k=x+x/2k, 由于0≤x′′<2k0 \le x'' < {2^k}0x<2k,故0≤x′′/2k<10 \le x''/{2^k} < 10x/2k<1,所以,x>>kx > > kx>>k的结果是丢弃了低k位,比实际结果小了x/2kx/{2^k}x/2k,其值在0到1之间,故符合向0舍入的预期;
  • 补码除以2k{2^k}2k
      假设补码x的二进制形式为[xw−1,xw−2,…,x0]\left[x_{w-1}, x_{w-2}, \ldots, x_{0}\right][xw1,xw2,,x0],设x′x'x[xw−1,xw−2,…,xk]\left[x_{w-1}, x_{w-2}, \ldots, x_{k}\right][xw1,xw2,,xk], x′′x''x[xk−1,xk−2,…,x0]\left[x_{k-1}, x_{k-2}, \ldots, x_{0}\right][xk1,xk2,,x0], 则x=x′∗2k+x′′x = x'*{2^k} + x''x=x2k+xx>>kx > > kx>>k的结果为x′x'x,有x′=x/2k−x′′/2kx' = x/{2^k} - x''/{2^k}x=x/2kx/2k, 且0≤x′′/2k<10 \le x''/{2^k} < 10x/2k<1。当x为正数时,符合向0舍入的预期,可是,如果x为负数时,结果却是向远离0进行取整的,这跟我们期望得到结果是背道而驰的。
      所以,对于补码的算术右移,为了让其满足向0舍入的结果,我们对小于0的情况加一个偏移量。因此C语言中算术右移的表达式为:
    (x<0 ? x+(1<<k)-1 : x) >> k

的结果为x′x'x,有x′=x/2k−x′′/2kx' = x/{2^k} - x''/{2^k}x=x/2kx/2k, 且0≤x′′/2k<10 \le x''/{2^k} < 10x/2k<1。当x为正数时,符合向0舍入的预期,可是,如果x为负数时,结果却是向远离0进行取整的,这跟我们期望得到结果是背道而驰的。
  所以,对于补码的算术右移,为了让其满足向0舍入的结果,我们对小于0的情况加一个偏移量。因此C语言中算术右移的表达式为:

    (x<0 ? x+(1<<k)-1 : x) >> k
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值