(一)信息的表示与处理

Chapter 2

# 信息的表示和处理 Bits Bytes & Intergers

## 位 整数

**为啥要用二进制?二进制有什么优点?在现实中有哪些应用?**

在计算机中,数据是以位/比特(bit)为单位储存的,一个字节是8bit的数据块,这个尺度是计算机设计早期确定的,所以沿用至今(_为什么这么设计?_ASCII `linux: man ascii`)

进制转换的操作要会哦,尤其是2进制,10进制,16进制的相互转化;其中二进制数存在最高有效位(MSB),最低有效位(LSB),十六进制和二进制转化极为方便.

> 这里我先插一嘴(哇哦),我们要熟练掌握十六进制和二进制之间的转化

**字长(word size)**对于计算机内存而言是很重要的概念,它标识了的内存地址的最大长度,而**地址的表示范围决定了内存的上限**.我们常说的 32 位系统,其内存地址空间最大为 4GB,不管你装多少根内存,能使用的就这么多,这是系统层面的限制,而更高级的 64 位系统,其内存上限达到 16EB,以人类目前的生产力,都很难凑出来这么大的内存.目前销售的计算机,绝大多数都是 64 位系统了.

> 32位系统不能运行64位程序,但是多亏AMD的x86-64架构,64位系统是可以运行32位程序的.

由于在不同编译器和系统上,int类型的长度可能会不同,C99中解决了这个问题,规定`int32_t`和`int64_t`来分别表示长度为4和8的整型数值,但是long的数据长度还是会根据32/64位系统变化.

计算机历史上还有大小端的派别之分,大端即数据高位在小地址,低位在大地址,反之则为小端法.

而且Windows,Linux,IOS和Android都是小端法.

ANSCII只适合表示英文文本内容,对于特殊符号很乏力,所以衍生了Unicode这一规范,后来的语言像java都支持Unicode文本编码,当然C也有补救的库.

接下来辨析两组设定:定点数和浮点数

计算机用二进制如何表示小数呢?要么固定小数点位置,固定读取,要么再花空间储存动态小数点的位置,这就是顶点和浮点的定义来源.定点整数就是整数啦,分为有符号和无符号,而小数(浮点数)的表示稍微复杂一点(后述).

布尔大兄弟发明的布尔代数,逻辑系统,增加了四类运算:

- 与`And`’&’

- 或`Or`’|’

- 非`Not` ‘'

- 异或`Xor` ‘^’

    异或这个东西有很多惊奇的用法,比如只用异或可以实现两变量交换值(注意!!如果x y数值相等,则结果为0);

```c

*x = *x ^ *y;

*y = *x ^ *y;

*x = *x ^ *y;

```

但是记得和逻辑运算符区分开,位运算符都是单符号运算符,逻辑运算符都是双符号的(除了非是!)

位运算还有两个小家伙,是左移和右移<<>>,就是将二进制数的小数点左右移动,相当于原数据除以/乘以2.位运算比乘法更快,可以作为特别情况下的简化.右移存在逻辑右移和算术右移,算术右移会按照最高位数值复制补位,而逻辑右移就是补0(大多数情况下,右移使用逻辑移位).

要注意,如果移位数量大于目标数据长度,编译器会先对移位数量取模(mod by length of target),用取模结果再进行移位.

C语言中各类数据的宽度

![](DraggedImage.png)

### 整型数

有无符号数唯一的区别就是符号位,无符号数全部宽度都用于储存数据内容,有符号数的最高位被用作符号位,导致表示范围减半.

在储存负数时,有符号数不是单纯的符号位置1,而是使用补码,即对应正数的二进制形式反转再+1,就得到对应负数的补码.**补码使符号位能与有效值部分一起参加运算,从而简化运算规则,并使减法运算转换为加法运算,进一步简化计算机中运算器的线路设计.**

三码概念辨析:

- 原码:符号位+数值绝对值

- 反码:原码中符号位不变,其他数值位和符号位做异或(即正数反码和原码一样,负数数值位会反转)

- 补码:正数的补码与其原码相同(正数三码合一),负数是反码的最低位+1.

有无符号数的转换:强制保位转换/软式保值转换,保位转换就是位模式不变,重新解读数值(按目标编码类型的规则解读),可能会有意外副作用:数值被+/-2^w

**当计算表达式中既有有符号数又有无符号数时,有符号自动强制转化为无符号数,这就会挖很多大坑,像一些宏变量都是unsigned int;另一些循环里涉及负数时,如果遇到无符号边界就可能出现奇怪的循环现象**

但是可以利用这个特性去做一些技巧实现,比如向下计数:

```c

unsigned i; 或者 size_t i;

for (i = cnt-2; i < cnt; i--)

a[i] += a[i+1];

```

**因此除非必要,建议初始化变量时都用unsigned数据**

有的时候数据宽度不够用了,那就得拓展一下子:符号拓展.无符号数直接左边高位填0扩展;有符号数进行拓展,从短整数类型向长整数类型转换时,C自动进行符号扩展.

有的时候不小心从长变短,就得截断一下(疼),那么多出的位直接截断,变量值根据剩余位数据重新解读,基本就是求模了.

可以再瞅瞅运算:

- 加:可能存在溢出,就要丢弃溢出位数据(忽略进位输出),有符号数还存在正/负溢出,减法合并到加法.

- 非:这没啥说的吧

- 乘:依然,溢出就丢弃,可以理解为运算完又做了一次取模,想要完整数据结果,需要使用一些特殊的包(_看看寄存器是怎么解决乘法溢出的?_)

- **除**:当除数为2的n次幂,那就右移n位即可;如果不是,就需要舍入除法(实际结果为浮点数,舍入后变为整数,避免类型不一致): 

对于无符号数和有符号正数,其实直接移位就可以23333,但是如果是有符号负数,需要先加一个偏移量(2^k-1),然后再右移n位,这样保证截断后是向零舍入,即: 

`x / k = ( x + (1<> k`

## 浮点数

浮点数这个东西十分滴有趣,由于小数点会自己动,可以表示非常大的数或者非常接近零的数,但是本质上还是2为基的一种表示方式,所以很多小数只能通过多位逼近表示其近似值,像0.27这种数.

IEEE(Institute of Electrical and Electronics Engineers)电子和电气工程师协会统一了规范,设置了IEEE 754这样的浮点规范:(有关IEEE 754的详细介绍 [点击链接](http://c.biancheng.net/view/314.html))

一个浮点数由符号(sign),尾数(mantissa/fraction/significand),基(base),阶(exponent)和一个点构成,对于计算机的浮点小数,可以用2位基的不科学计数法表示:Mx2^E.

在32/64位浮点数储存的格式不同,float浮点数中,第0位是符号位,1-8位是阶码位,9-31是尾数位;在double中,0是负号,1-11是阶码,12-63是尾数

但是你想得太简单了!(我也天真了),他们分了规格化数和非规格化数,咱抠一抠吧:

- **规格化数**:浮点数阶码位不全为0/1时为规格化数范围 

但是阶码采用偏置值编码,exponent=E+Bias,其中Bias的值在32/6位浮点数中分别为127/1023. 

尾数编码时,默认第一位为1(二进制),这样节省了一位精度,只需要表示M中小数点后面的数据即可,当frac段为全0时,这个M就是1(最小的规格化数),当frac全为1时,M趋近于2,是最大值.

- **非规格化数**:这个老哥可以表示规格化数范围外的数据,很有用. 

exp=000…000时,**E=1-Bias**,尾数里默认第一位是0,这样就能表示0附近的数(lim-\>0)了,此时又会出来一对正负0. 

exp=111…111时,当frac=0 就可以表示正负无穷(除零错误),如果frac!=0,那输出的就是NaN了

![](DraggedImage-1.png)

数说完了,那咋计算呢?浮点数运算有个基本思想,就是先算精确值,然后根据输出变量类型进行相应转化,是舍入还是溢出了etc.

说到舍入呢,有四种舍入方法,最简单的俩就是向上舍入和向下舍入,不管小数位是啥直接进位或者舍去,另外还有向零舍入和向偶数舍入.最后这个**向偶数舍入**是重点,也是默认舍入模式.

**偶数舍入**:当小数不等于中间值时就采用向上或者向下舍入,等于中间值的时候判断哪边舍入之后变成偶数形式(小数最后一位为偶数,二进制时舍入位右侧全为0)即可.

回到计算,我们只需要关注乘法和加法就可以,其他的运算大同小异.

- **乘法**:阶码相加尾数相乘,这个不用多说,数学知识.当尾数相乘后\>2了,那就进个位,右移一下,E+1就完事了;但是也有可能溢出,乘积太大了呗,那就会报错了.运算最后肯定加一步舍入,这个前面刚说.

- **加法**:首先得对齐小数点再相加,结果的阶码等于加数里最大的E(对齐小数点了嘛),进位溢出舍入的操作和乘法都相似.

前面说的都是浮点数运算的通用规则和设定,在C语言中还有一些注意事项:

1. int转float,不会溢出,但是可能舍入

2. 低精度转高精度,不会丢数据(废话),反过来可能造成溢出/舍入,丢数据(还是废话)

3. 从浮点直接转int的话,就咔嚓一下截断了

## 内存 指针 字符串

内存就是个超大数组,线性存着一大堆数据,内存地址就是数组的索引,指针就是索引的值.从8章可知,其实每个进程都有属于自己独占的内存空间,高度抽象的进程管理方式.

字长:地址数据宽度,如果是32位地址,那内存最大4G(32位系统内存上限),64位地址,那内存上限老大了(懒得算),计算机目前使用的是面向”字”的内存组织管理,每个字占4/8字节

字节排列顺序(从小到大/从大到小)就分为小端法和大端法,这个随操作系统变化,可以利用指针/共用体输出一个变量的低位/高位数据值来判断.

字符串就是ASCII码表示咯!

你可能感兴趣的:((一)信息的表示与处理)