CS:APP读书笔记--信息的表示和处理

信息的存储和表示

字节端序

在内存中按照从最低到最高有效字节的顺序存储对象,这种最低有效字节在最前面的方式,称为小端法

在内存中按照从最高到最低有效字节的顺序存储对象,这种最高有效字节在最前面的方式,称为大端法

例如:对于int32类型变量,其存储地址位于0x100,其十六进制值为0x12345678,其地址范围为ox100~0x103

// 大端法
地址:0x100 0x101 0x102 0x103
     12    34    56    78

// 小端法
地址:0x100 0x101 0x102 0x103
     78    56    34    12

大端法表示与正常书写时的字节顺序一致。

  • 按照字节序列打印数据
#include 

typedef unsigned char* byte_pointer;

void show_bytes(byte_pointer start, int len) {
    int i;
    for (i = 0; i < len; i++) {
        printf("%02x ", start[i]);
    }
    printf("\n");
}

void show_int(int x) {
    show_bytes((byte_pointer) &x, sizeof(int));
}

void show_float(float x) {
    show_bytes((byte_pointer) &x, sizeof(float));
}

void show_pointer(void* x) {
    show_bytes((byte_pointer) &x, sizeof(void*));
}

在使用ASCII码作为字符码的任何系统上都将得到相同的结果,与字节顺序和字节大小规则无关。因而,文本数据比二进制数据具有更强的平台独立性

位移运算

位移表示对一个二进制位向左位移和向右位移。

向左位移:左移k位,并丢弃最高的k位,并在最后补0。

**向右位移:**向右位移分为逻辑右移和算术右移。

  • 逻辑右移:向右移k位,并在左端补k个0;
  • 算术右移:向右移k位,并在左端填充符号位。(几乎所有机器或编译器都使用算术右移)
// 算术右移 0x8A -118 >> 3 = -15
1000 1010 // -118
// 右移3位,由于是负数,因此在最左边3位补 1
1111 0001 // -15

整数表示

整数取值,负数的范围比正数大1。一般定义:

#define INT_MAX 0x7fffff
#define INT_MIN -INT_MAX - 1

无符号数编码:

1001: 1 * 2^3 + 0 * 2^2 + 0 * 2^1 + 1 * 2^0 = 9
1010: 1 * 2^3 + 0 * 2^2 + 1 * 2^1 + 0 * 2^0 = 10
1111: 1 * 2^3 + 1 * 2^2 + 1 * 2^1 + 1 * 2^0 = 15

补码编码:最常见的有符号数的计算机表示方式就是补码。补码中,将最高有效位解释为负权。

10011 * 2^(-3) + 0 * 2^2 + 0 * 2^1 + 1 * 2^0 = -7
1010: 1 * 2^(-3) + 1 * 0^2 + 1 * 2^1 + 0 * 2^0 = -6
1111: 1 * 2^(-3) + 1 * 2^2 + 1 * 2^1 + 0 * 2^0 = -1

有符号数和无符号数之间的转换:强制类型转换的结果保持位值不变,只是改变了解释这些位的解释方式。

  • 补码转换位无符号数:w位的补码数字, 当 x >= 0时,直接转换;当x < 0时为-2^w + x;
  • 无符号数转为补码: w位的补码数字, 当x <= 2^(w-1) - 1时,直接转换;当x > 2^(w - 1) - 1时,为x - 2^w
short int v = -12345; 
// 1100 1111 1100 0111 ->
unsigned short uv = (unsigned short) v;
printf("v = %d, uv = %u\n", v, uv); // v = -12345, uv = 53191

对于C语言,如果一个数是有符号,另一个是无符号,那么C语言会隐式地将有符号参数强制转换为无符号数,并假设这两个数都是非负数。

// INT_MAX = 2147483647 INT_MIN -2147483648

int b1 = -1 < 0;	// 1
int b2 = -1 < 0;	// 1
int b3 = 2147483647 > -2147483647 - 1;	// 1
int b4 = 2147483647U > -2147483647 - 1;	// 0
int b5 = 2147483647 > -2147483647 - 1U; // 0

扩展一个数字的位表示

  • 无符号数:简单的在高位填充0;
  • 有符号数:最高位填充符号位。需要先改变大小,再完成从有符号位到无符号的转换。

截断数字

  • 截断无符号数:等价于计算该值模2^w,x mod 2^k
  • 截断补码数字:位阶与截断无符号数一致,可以先按照无符号数截断,再按照补码解释,即最高位的权重为-2^w

整数运算

无符号加法:对满足 0 <= x, y <= 2^w

  • x + y < 2^w时,正常输出,为x + y

  • -2^w <= x + y < 2^(w + 1)时,发生溢出,此时结果为x + y - 2^w

  • 判断无符号加法是否溢出:x + y >= x表示没有溢出,否则溢出了

无符号数取反:对满足 0 <= x < 2^w

  • x = 0时,返回x
  • x > 0时,返回2^w - x

补码加法:对满足-2^(w-1) < x, y < 2^(w-1) - 1

  • x + y >= 2^(w-1)时,结果减去2^w,表示为x + y - 2^w
  • -2^(w-1) <= x + y < 2^(w-1),正常输出为x + y
  • x + y < -2^(w-1)时,结果加上2^w,表示为x + y + 2^w
  • 补码加法与无符号加法在位级表示上一致

补码取反:对满足-2^(w-1) < x < 2^(w-1) - 1

  • x = TMin时,返回TMin

  • x > TMin时,返回 -x

  • 计算:-x = ~x + 1

    // 例如 5 -> -5,0101 -> 1011
    5 -> 0101
    ~5 -> 1010 = -6-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    ~5 + 1 -> 1010 = -5
    

    无符号乘法:对满足 0 <= x < 2^w,其结果为(x.y)mod2^w,在大多数机器上,乘法指令很慢,需要10个或更多时钟周期。

    补码乘法:对满足-2^(w-1) < x, y < 2^(w-1) - 1

    • 补码乘法与无符号乘法在位级别上表示一致,x * K
      • 形式A:K = 2^a + 2^b + 2^c => (x<
      • 形式B:K = x^(a + 1) - 2^b => x<<(a+1) - x<
    • 因此可以先按照无符号乘法计算,再转换为补码表示

    除以2的幂:整数除法比乘法更慢,需要30个或更多时钟周期。除以2的幂可以用右移来实现。

    • 无符号数右移是逻辑右移:逻辑右移k位与除以2^k再舍入到零(舍去小数部分)是相同的结果
    • 补码右移是算术右移:算术右移是向零舍入,补码除法是向下舍入。可以在移位之前,偏置这个值,来修正这种不合适的舍入,(x + (1<> k

    浮点数

二进制浮点数表示:101.11 = 1 * 2^2 + 1 * 2^0 + 1 * 2^(-1) + 1 * 2^(-2) = 4 + 1 + 1/2 + 1/4 = 23/4.

小数的二进制表示法只能表示那些能够被写为x + 2^y的数

IEEE浮点数表示V=(-1)^s * M * 2^E 。浮点数的三个基本组成部分:符号位、阶码位(或指数位)和尾数位(或小数位):

  • s为示符号位。这是一个二进制位,用于表示浮点数的正负。0代表正数,1代表负数。
  • E为阶码。用于表示浮点数的指数,即小数点相对于尾数部分移动的位置。在IEEE 754中,阶码通常采用偏移量的表示方式,这样可以表示更大范围的指数。对于单精度浮点数(32位),阶码占8位,实际存储的是e-Bias,其中e是实际指数,Bias是一个预设的偏移量(对于单精度,Bias是127;对于双精度,Bias是1023)。
  • M为尾数。也称为小数位,记录了浮点数的有效数字。在IEEE 754中,尾数通常是一个二进制小数,而且为了节省空间,会省略掉最高位的1(因为对于正常的非零数,这个位置总是1),所以实际存储的是小数点后的部分。

IEEE 标准还定义了一些特殊值,用于表示特定的数学情况或错误:

  • 无穷大:当阶码位全为1,尾数位全为0时,表示无穷大。如果符号位为0,则是正无穷大;如果为1,则是负无穷大。
  • NaN:当阶码位全为1,尾数位不全为0时,表示非数值。这通常用于表示无效的数学运算,比如0除以0或对负数开根号等。
  • 非规格化数值:当阶码位全为0,表示非规格化数值,阶码值为1-Bias。主要有两个用途:
    • 表示0:当阶码位与小数位全为0时,表示0。而根据符号位的不同,还可以表示为+0.0-0.0
    • 表示非常接近0.0的数,它们提供了一种属性,称为下溢。
/* 例如:8位的浮点数,阶码位为4,小数位为3。Bais = 2^4 - 1 = 7*/

/* 非规格化数值:阶码计算为 1 - Bais = -6 */
0 0000 000		
/* 表示+0.0 */
0 0000 001		
/* 最小的非规格化数:
 * 权:E = 1 - 7 = -6,权 2^E = 1/64
 * 小数部分 f = 0 * 2^(-1) + 0 * 2^(-2) + 1 * 2^(-3) = 1/8
 * 值: 1/64 * 1/ 8 = 1/512
 */
0 000 1119-+8*/7····z
    468*8*8*8*8*8*
/* 最大的非规格化数:
 * 权:E = 1 - 7 = -6,权 2^E = 1/64
 * 小数 f = 1 * 2^(-1) + 1 * 2^(-2) + 1 * 2^(-3) = 7/8
 * 值:1/64 * 7/8 = 7/512
 */
    
/* 规格化值:阶码计算为 2^e - Bais */
0 0001 000
/* 最小的规格化值
 * 权:E = 1 - 7 = -6,权 2^E = 1/64
 * 小数:1 + 0
 * 值:1/64 * 1
 */
0 0111 000
/* 1
 * 权:7 - 7 = 0 权:1
 * 小数:1 + 0
 * 值:1 * 1
 */
0 1110 111
/* 最大的规格化数
 * 权:E = 14 - 7 = 7 权:2^E = 128
 * 小数:1 + 7/8 = 15/8
 * 值:128 * 15/8 = 240.0
 */
    
/* 特殊值 */
0 1111 000	/* +0.0 */
1 1111 000  /* -0.0 */

IEEE 标准定义了两种主要的浮点数类型:单精度和双精度:

  • 单精度浮点数:使用32位表示,其中1位是符号位,8位是阶码位,23位是尾数位。
  • 双精度浮点数:使用64位表示,其中1位是符号位,11位是阶码位,52位是尾数位。

舍入:对于值x,能够找到最接近的匹配值,它可以用期望的浮点数表示出来,这就是舍入运算的任务。IEE有四种舍入方式:

  • 向偶数舍入:也被称为向最近的值舍入,是默认的方式。其规则如下:

    • 如果小数部分小于0.5,则舍去小数部分(即向下舍入);
    • 如果小数部分大于0.5,则向上舍入;
    • 如果小数部分等于0.5,则舍入到最近的偶数。
    2.4 -> 2
    2.6 -> 3
    2.5 -> 2 /* 小数部分是 0.5,最后一位是偶数,则舍去小数,返回 2 */
    1.5 -> 2 /* 小数部分是 0.5,最后一位是奇数,则向上舍入,返回 2 */
    
  • 向零舍入:直接截尾,不考虑小数部分的值,即向数轴零点方向舍入。x向零舍入为x0,那么|x0| < |x|

  • 向上舍入:把正数和负数都向下舍入。x向上舍入得到 x+,那么x <= x+

  • 向下舍入:把正数和负数都向下舍入。x向下舍入得到x-,那么x- <= x

你可能感兴趣的:(CS:APP,笔记,程序人生)