《深入理解计算机系统》----第二章:信息的表示和处理

1、信息存储

  • 大多数计算机使用8位的块(称为字节(byte)),作为最小的可寻址的内存单位,而不是去访问单独的一个位。

  • 机器级程序将计算机的内存看做是一个很大的字节数组,称作虚拟内存。虚拟内存的每个字节都由唯一的数字来标识,称它为地址

1.1 十六进制表示法

  • 一个字节有8位,即用二进制表示会有八位数,这样表示会比较冗长,所以使用最多的还是十六进制数(如0xA9),一个位十六进制数就可以表示一个8位的二进制数。

  • 同理,一个字节可以由两个十六进制数表示。

  • 各个进制的值域

# 二进制值域:
    0000 0000 ~ 1111 1111
# 十进制值域:
    0 ~ 255
# 十六进制值域:
    00 ~ FF (注意:A-F,字母可以大小写,也可组合大小写。十六进制表示为0X或者0x开头)

# 其他进制转换为十进制(重点):
    八进制:按权展开,每位乘以 8 的幂,然后相加。
    十六进制:按权展开,每位乘以 16 的幂,然后相加。对于 A 到 F,用 10 到 15 来代替,依此类推。
    二进制:按权展开,每位乘以 2 的幂,然后相加。
​
# 例如:十进制 13
    二进制:0000 1101
    八进制:000 001 101 = 0 1 5 == 15
    十六进制:0000 1101 = 0 13 = C
​
# 例如:
    十进制数 42 转换为八进制:42 ÷ 8 = 5 余 2,所以八进制为 52。
    十进制数 42 转换为十六进制:42 ÷ 16 = 2 余 10(A),所以十六进制为 2A。
    十进制数 42 转换为二进制:42 ÷ 2 = 21 余 0,21 ÷ 2 = 10 余 1,所以二进制为 101010。

1.2 字和数据大小

  • 每台计算机都有字长(32位或64位,这里的概念注意跟字节区分),指明指针数据的标称大小。

  • 因为虚拟地址是一个字长来表示的,所以字长的大小决定了虚拟地址的寻址大小。

  • 在编译的时候就会决定一个程序是32位程序还是64位程序

  • C语言中的char型就表示一个单独的字节,尽管它是用来保存单个字符而得名。

1.3 寻址和字节顺序

  • 在几乎所有的机器上,多字节对象都被存储为连续的字节序列,对象的地址就是字节序列中最小的地址。

    • 大端字节序Big-Endian):在大端字节序中,指数据的低位保存在内存的高地址中,而数据的高位保存在内存的低地址中。

    • 小端字节序Little-Endian):在小端字节序中,指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中。

1.4 表示字符串

  • C语言中字符串被编码以一个以null(其值为0)字符结尾的字符数组

  • 字符串中的每个字符都是以某个标准编码来表示,最常见的就是ASCII码。

#include <stdio.h>
// 生成ascii码
int main(){
    int i;
    for(i=1; i<= 127; i++){
        printf("%d --> %c\n",i ,i);
    }
}

1.5 表示代码

  • 代码在不同机器上编译成机器指令时,字节码的表示是完全不同的。

  • 从计算机的角度来看,一个程序仅仅是一个字节序列。除了可能用来帮助调试的表以外,机器没有关于原始源程序的任何信息。

1.6 布尔代数简介

  • 逻辑值(真)和逻辑值(假)分别对应二进制编码1和0

  • 布尔代数运算:

1.7 C语言中--位级运算

C语言的一个很有用的特性就是它支持按位布尔运算.

# C语言所使用的: 
    |就是OR (或) 
    &就是AND (与)
    ~就是NOT (取反)
    ^就是EXCLUSIVE-OR (异或)

这些运算能运用到任何“整型”的数据类型上,也就是那些声明为char或者int的数据类型,无论它们有没有像shortlonglong long 或者unsigned这样的限定词。以下是- -些对char数据类型表达式求值的例子。

1.8 C语言中--逻辑运算

C语言还提供了一组逻辑运算符|&&!,分别对应于命题逻辑中的ORANDNOT运算。

1.9 C语言中--移位运算

C语言还提供了一组移位运算,以便向左或者向右移动位模式。在C语言中,左移运算符用 << 表示,右移运算符用 >> 表示。

  • 逻辑右移算术右移的区别与用法

    • 逻辑右移(>>):

      • 逻辑右移是将二进制数的每一位向右移动指定的位数,同时在左侧补零。

      • 逻辑右移主要用于无符号整数,不考虑符号位,只是简单地移动二进制数的位。

      • 示例:对于二进制数 10101010,逻辑右移 2 位得到 00101010。

    • 算术右移(>>):

      • 算术右移是将二进制数的每一位向右移动指定的位数,同时在左侧使用原最高位(即符号位)进行填充。

      • 算术右移可以用来实现有符号数的除法运算,并保持结果的符号不变。

      • 示例:对于二进制数 10101010,算术右移 2 位得到 11101010。

// 左移运算示例
int a = 5;  // a的二进制表示为101
int b = a << 2;  // 将a左移2位,b的值为10100,即20
​
// 右移运算示例
int c = 10;  // c的二进制表示为1010
int d = c >> 1;  // 将c算术右移1位,d的值为0101,即5
​
// 使用移位运算实现二进制位操作
int e = 0x12345678;
int f = (e & 0xFF) << 24;  // 将e的最低字节作为掩码,提取出来后左移24位

 

2、整数表示

编码整数的两种不同的方式:一种只能表示非负数,而另一种能够表示负数正数

  • n位计算机的计量范围为0~2n-1,模为2n

  • :计算机产生的溢出的量,与计算机的位数有关

  • 反码:在原码的基础上,符号位保持不变,其余按位取反

  • 补码:补码的符号是其数值的一部分,正数的补码是其本身,负数的补码为反码+1

  • 补码非:最右1之前取反(不包括最右1)

2.1 整数的数据类型

  • 32位机器中C语言的整数数据类型

C数据类型最小值最大值
char-2727-1
unsigned char028-1
short [int]-215215-1
unsigned short [int]0216-1
int-231231-1
unsigned [int]0232-1
long [int]-231231-1
unsigned long [int]0232-1
long long [int]-263263-1
unsigned long long [int]0264-1
  • 64位机器中C语言的整数数据类型

C数据类型最小值最大值
char-2727-1
unsigned char028-1
short [int]-215215-1
unsigned short [int]0216-1
int-231231-1
unsigned [int]0232-1
long [int]-263263-1
unsigned long [int]0264-1
long long [int]-263263-1
unsigned long long [int]0264-1

2.2 有符号与无符号数的表示(正负数编码)

  • 无符号编码主要看位的权值,正常编码;

  • 有符号(负数)编码通常使用补码编码。最高位有效位为符号位,符号位为1,表示值为负;符号位为0,表示值为正。

负数的二进制表达就是通过二进制补码的方式进行编码的。二进制补码是一种表示负整数的方法,它的原理是将负整数转化为它的绝对值的二进制形式,然后按位取反并加1。

具体步骤如下:例如,我们要将 -13 转换为二进制。

# 1. 将负数取绝对值,并转换为二进制表示。
    -13 的绝对值为 13,二进制表示为 0000 1101。
# 2. 对这个二进制数按位取反,即将 0 变为 1,将 1 变为 0。
    取反得到:1111 0010
# 3. 将上一步得到的结果加 1。
    加 1 得到:1111 0011
# 所以,-13 的二进制表示为 1111 0011。十六进制表示为 0xF3
​
# 这样,通过二进制补码的方式,负数可以用二进制进行表示和计算,便于在计算机中进行处理。
# 需要注意的是,在使用二进制补码表示法时,最高位的符号位为1,代表负数。

2.3 扩展一个数字的位表示

目的:从一个较小的数据类型转换成一个较大的数据类型

  • 无符号数的零扩展

    • 在进行无符号数的零扩展时,将一个较短的无符号数转换为一个较长的无符号数,通过在高位添加零来填充。

    • 这样可以保持数值不变,只是扩展了表示的位数。

    • 例如,将4位的无符号数0110进行零扩展为8位,得到00000110。

  • 补码数的符号扩展

    • 在进行补码数的符号扩展时,将一个较短的补码数转换为一个较长的补码数,通过在高位添加符号位来填充。

    • 这样可以保持数值相同的同时,扩展了表示的位数。

    • 例如,将4位的补码数0101进行符号扩展为8位,得到11110101。

注意!符号扩展仅适用于有符号数的补码表示。在进行符号扩展时,要根据原始数值的符号位来判断添加的是0还是1。如果原始数值是负数,需要添加1作为符号位;如果原始数值是正数,需要添加0作为符号位。这样可以保持数值的符号不变,只是扩展了表示的位数。

2.4 截断数字

如:强制类型转换时,将32为的int 截断成为16位的short int

  • 截断无符号数

    • 截断无符号数是指将一个较长的无符号数转换为一个较短的无符号数,通过去掉高位来实现。

    • 这样可以将表示的位数减少,但会丢失部分数值信息。

    • 例如,将8位的无符号数11001010进行截断为4位,得到1010。

  • 截断补码数值

    • 截断补码数值是指将一个较长的补码数转换为一个较短的补码数,通过去掉高位来实现。

    • 与无符号数相比,截断补码数需要保留符号位。这样可以将表示的位数减少,但同样会丢失部分数值信息,同时可能破坏原始数值的符号。

    • 例如,将8位的补码数11101101进行截断为4位,得到1101。

注意!数据是否超过新的位数范围。如果数据的高位被截断掉后,仍然能够在新的位数范围内表示,则截断操作是安全的。但如果截断后的数据超过了新的位数范围,则无法准确表示原始数值,可能导致结果错误或溢出。

3、整数运算

主要是有符号与无符号数的加、减、乘、除运算。

3.1 加法

  • 无符号加法

    • 无符号加法是指在进行相加运算时,将两个无符号数按照二进制相加的规则进行计算,忽略符号位。

    • 无论是执行加法还是减法,结果都是通过对两个二进制数进行逐位相加,并考虑进位的方式得到的。

    • 如果产生了进位,则将进位标记为1,否则标记为0。无符号加法不会考虑数值的正负情况,只是对数值的二进制表示进行运算。

# 3+2
    3: 0011
    2: 0010
    3+2:    0011
          + 0010
        ------------
          = 0101 
          = 5
  • 补码加法

    • 补码加法是指在进行带符号数相加的运算时,将两个补码数按照二进制相加的规则进行计算。

    • 对于有符号数的补码表示,最高位(即符号位)表示数值的正负。补码加法需要同时考虑数值的二进制表示和符号位。

      • 补码加法的规则与无符号加法类似,但需要额外处理溢出和符号位的情况。

      • 当两个正数相加产生负数时,称为正溢出

      • 当两个负数相加产生正数时,称为负溢出

# 例如,对于补码数3(二进制表示为00000011)和-2(二进制表示为11111110)进行相加,按位相加的结果为:00000001
3+(-2):注!,-2的二进制需要进行补码,则首先2:0000 0010,再进行反码:1111 1101,再进行加1,得到 1111 1110
0000 0011 + 1111 1110 = <1> 0000 0001  这里第九位为溢出,然后0000 0001的最高位为0,所以为正数。
  • 补码的非

    • 补码的取反操作是将补码的每一位取反(0变为1,1变为0),得到的结果称为补码的非。

# 以-2的补码形式11111110为例,进行补码的非操作:1 1 1 1 1 1 1 0

# -2的补码的非为 11111110 的补码形式为 00000001,即1。

3.2 乘法

  • 无符号乘法

    • 无符号乘法是指在不考虑符号位的情况下进行乘法运算。

    • 在无符号乘法中,二进制数的每一位都按照相乘的方式进行计算,没有考虑符号位。

# 首先,我们需要将需要相乘的两个数字表示为二进制形式,并对齐:
       0 1 0 1    (5)
     × 0 0 1 1    (3)
    ------------------
# 然后,从右向左依次对齐两个数的每一位,并将其相乘。将每次相乘的结果写在一条对应的斜线上:
       0 1 0 1    (5)
     × 0 0 1 1    (3)
    ------------------
       0 1 0 1    (5×1)
     0 1 0 1      (5×2)
   0 0 0 0        (5×4)
+0 0 0 0		  (5×4)
-----------------------------
  	   1 1 1 1    (结果)
  • 补码乘法

    • 补码乘法可以通过将两个数的补码进行相乘,然后再将结果的补码表示转换回原码表示来得到最终的乘积。

    • 正数的补码计算: 对于正数而言,它的补码与其二进制原码表示相同。

    • 负数的补码计算: 对于负数而言,补码是通过将其绝对值的二进制表示取反(按位取反),然后再加1得到的。

# 正数的补码乘法运算(3 * 5 = 15)
       	   0 1 0 1    (5)
         × 0 0 1 1    (3)
        ------------------
           0 1 0 1    (5×1)
         0 1 0 1      (5×2)
       0 0 0 0        (5×4)
   	+0 0 0 0		  (5×4)
    -----------------------------
    0 0 0 0 1 1 1 1    (结果)

# 负数的补码乘法运算(3 *(-3)= -9)
          0 0 0 0 0 0 1 1		(3)正数的补码是原二进制
        * 1 1 1 1 1 1 0 1		(-3)负数的补码是,将原值的绝对值的二进制进行取反,在加1.
    -----------------------------
			  0 0 1 1
			0 0 0 0
		  0 0 1 1
	    0 0 1 1
    -----------------------------
        1 1 1 0 1 1 1

3.3 乘以常数

因为在机器上,乘法指令需要10个甚至更多的时钟周期,所以一般使用其他运算(如加法、减法、位级运算和移位)来代替。

  • 乘以2的幂【无符号】

    • 左移k位,即 x<<k 就可以产生 x*2k

  • 乘以2的幂【补码】

    • 与无符号相同,即使结果溢出【可以截断】,得到的结果也是一样的。

3.4 除法(除以2的幂)

  • 除以2的幂【无符号】

    • 右移k位,即 x>>k 就可以产生 x/2k

  • 除以2的幂【补码】

    • 对补码来说,与无符号除法不同的是为了保证负数仍然是负数,要执行算术右移

4、浮点数

4.1 二进制小数

  • 使用二进制表示小数,若遇到《整数.小数》例如:10.5

# 1、将小数部分乘以2,并将结果的整数部分作为二进制的一位。保留结果的小数部分。

# 2、继续将上一步的结果乘以2,并将新的整数部分添加到二进制表示中。再次保留结果的小数部分。

# 3、重复上述步骤,直到小数部分为0或达到所需的精度。

# 举个例子,我们将0.375转换为二进制表示:
	将小数部分0.375乘以2得到0.75。整数部分为0,所以二进制表示的第一位为0。
	将0.75乘以2得到1.5。整数部分为1,所以将1添加到二进制表示的后面。小数部分为0.5。
	将0.5乘以2得到1.0。整数部分为1,将1添加到二进制表示的后面。小数部分为0,转换结束。
# 因此,0.375的二进制表示为0.011。
  • 小数的二进制转换成小数

    • 按照以下步骤进行:

# 1、确定二进制数的整数部分和小数部分。以小数点为界,将二进制数分为整数部分和小数部分。
# 2、将整数部分转换为十进制数。从二进制数的右侧开始,每位数字乘以2的幂次方,幂次方从0开始,每次加1,直到得到左侧最高位对应的幂次方。将各个位的乘积相加即可得到整数部分的十进制值。
# 3、将小数部分转换为十进制数。从二进制数的左侧开始,每位数字乘以2的负幂次方,幂次方从-1开始,每次减1,直到得到右侧最低位对应的幂次方。将各个位的乘积相加即可得到小数部分的十进制值。
# 4、将整数部分和小数部分的十进制值相加,即得到二进制数对应的十进制小数。

# 举个例子,假设要将二进制数1101.101转换为十进制数:
    1、确定整数部分为1101,小数部分为0.101。
    2、将整数部分转换为十进制:1×2³ + 1×2² + 0×2¹ + 1×2⁰ = 8 + 4 + 0 + 1 = 13。
    3、将小数部分转换为十进制:1×2^-1 + 0×2^-2 + 1×2^-3 + 1×2^-4 = 0.5 + 0 + 0.125 + 0.0625 = 0.6875。
    4、整数部分和小数部分的十进制值相加:13 + 0.6875 = 13.6875。

4.2 IEEE浮点表示

IEEE浮点数表示法是一种用于表示浮点数的标准化方法,它由国际电气与电子工程师协会(IEEE)制定并广泛应用。 IEEE 754标准定义了两种常见的浮点数表示格式:单精度(32位)和双精度(64位)。

IEEE浮点标准用V=(-1)s*M*2`E`的形式来表示一个数:

  • 符号(sign):s决定这个数是负数(s=1)还是正数(s=0),而对数值为0的符号位解释作为特殊情况处理。

  • 尾数(significand):M是一个二进制小数,它的范围是1~2-ℇ或者是0~1-ℇ

  • 阶码(exponent):E的作用是对浮点数加权,这个权重是2的E次幂(可能是负数)。

  • 将浮点数的位表示划分为三个字段, 分别对这些值进行编码:

  • 下面将详细介绍这两种格式的表示方法

    • 单精度(32位)浮点数表示:

# 单精度浮点数由32位二进制位组成,按照如下布局划分为三个部分:
    符号位(1位):确定数值的正负性。0表示正数,1表示负数。
    指数位(8位):用于表示指数部分的偏移量。通过对实际指数加上一个固定的偏移量进行表示,使得指数可以有正负范围。
    尾数位(23位):用于表示小数部分的有效数字。
# 具体表示方法如下:
    符号位: 1位
    指数位: 8位
    尾数位: 23位
  • 双精度(64位)浮点数表示:

# 双精度浮点数由64位二进制位组成,同样按照一定的布局划分为三个部分:
    符号位(1位):确定数值的正负性。0表示正数,1表示负数。
    指数位(11位):用于表示指数部分的偏移量。
    尾数位(52位):用于表示小数部分的有效数字。
# 具体表示方法如下:
    符号位: 1位
    指数位: 11位
    尾数位: 52位

4.3 舍入

因为表示方法限制了浮点数的范围和精度,浮点运算只能近似地表示实数运算。因此,对于值x,我们一般想用一种系统的方法,能够找到“最接近的","匹配值x",它可以用期望的浮点形式表示出来。这就是舍入(rounding) 运算的任务。

4.4 浮点运算

浮点运算是对浮点数进行数学运算的过程。在计算机中,浮点数的表示和运算遵循IEEE 754浮点数标准。常见的浮点运算包括加法、减法、乘法和除法。

下面分别介绍它们的基本原理:

  • 加法运算

    • 对于两个浮点数的加法,首先需要比较它们的指数大小,并将指数较小的数进行无损右移,使得两个浮点数的指数相等。

    • 然后将尾数对齐后进行相加,并按照规定的浮点数位数进行舍入。最后,根据运算结果的范围和精度进行溢出处理。

  • 减法运算

    • 减法运算可以转换为加法运算,即将减数取负值,然后进行加法运算。

  • 乘法运算

    • 乘法运算涉及到尾数的相乘和指数的相加。

    • 首先将两个浮点数的尾数相乘,然后将指数相加,并根据运算结果的范围和精度进行舍入和归一化处理。

  • 除法运算

    • 除法运算涉及到尾数的相除和指数的相减。

    • 首先将两个浮点数的尾数相除,然后将指数相减,并根据运算结果的范围和精度进行舍入和归一化处理。

浮点运算中可能存在舍入误差,尤其在进行多次运算时。这是由于浮点数的表示精度有限,无法精确表示所有实数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值