数值系统

发布时间 2023-09-23 23:05:10作者: Stargazer4u

数值系统

学习的时候不但要知其然,而且要知其所以然。
比如为什么要用这种表示方式?有什么好处?

二进制数表示方式

原、反、补码及其缺点和补码好处

原码
最初,人们直接使用二进制原码的第一位作为符号位,剩下的位为大小,例如:

2  ---> 0010
-2 ---> 1010

这样问题在于计算机做加法的时候需要额外判断两个数的正负,而且有+0和-0。
运算规则是: 若同号则直接相加,异号则用绝对值相减,最终根据大小决定正负。(实际和这个可能不一样,此处主要说明他算起来不方便)

反码
人们使用反码解决了需要额外判断正负号的问题。反码在原码的基础上,对负数的非符号位取反(注意:正数三种码都和原码一样),做加法的时候只需要直接开加即可,如果有负数最终要额外再加上一个1就是正确答案,依然有点麻烦,例如

-2 ---> 1010  //原码
-2 ---> 1101  //反码

-2 + 5 = 3
 1101
+0101
+0001 //额外加一
=0011 ---> 3

-1 + -1 = -2
1110
1110
0001
1101 --->-2

缺点是仍然存在+0和-0,而且需要判断有没有负数

补码
反码基础上加一就是补码,补码码运算的时候不必加上最后的1,不用判断正负数。
补码还有好处是:运算结果连续,没有+0 -0。
至于他为什么可以直接相加得到最后的答案,用数学知识可以证明,此处略。

-2 + 5 = 3
1110
0101
0011 ---> 3

范围和溢出

讨论范围要分有符号和无符号

无符号
这个比较简单 $[0,2^k-1]$ ,如果发生溢出,会从头开始

//unsigned 4_bits
0000-1111
1111 + 0001 = 0000

有符号
正数,符号位占一位,所以范围只有: $[0, 2^{k - 1} - 1]$
负数,计算方式为 $-2^k + 剩下部分直接当正数计算$ 所以范围是 $[-1, -2^k]$

//负数举例
1111 = -2^4 + 7 = -9
1000 = -2^4 + 0 = -16

有符号的溢出,会变成异号,很好理解,最高位发生改变。

0111 + 1 = 1000 ---> 7 + 1 = -16
1000 - 1 = 1000 + 1111 = 0111 ---> 7

他们的范围可以看作一个圆,从0到正最大,再迈出一步到负最小,再继续增加到-1,0.
相当于把数轴两端粘到一起。

注意
C++中无符号和有符号运算的时候会发生一些自动转换,容易出错。例如

vector<int> nums;
//size()返回的size_t是无符号的,所以若size() == 0,会变得很大
for (int i = 0; i < nums.size() - 1)

浮点数

格式

第一位表示正负(s),一些位数表示小数值(f位),一些位数(e位)用于表示指数大小,off是用于偏移指数的值
$$x = (-1)^s * (1 + f) * 2 ^ {(e - off)}$$
注意上述公式中f是小数部分的二进制位
e是指数的真实大小,off的作用是将指数范围都转到正数,使得e - off指数这个整体是正数,不必另外处理指数的正负,最终存储的是e - off的二进制位,举例:

假设我们有一个单精度浮点数:
0 10000010 11010000000000000000000
其中:
符号位:0,表示正数
指数位:10000010,偏移量为 127,所以真实指数值为 10000010 - 127 = -25
尾数位:11010000000000000000000

注意:尾数位计算应该倒着看,比如上面的二进制算十进制:

$$(-1)^0 * (1.1011)_2 * 2 ^ {(-25)} $$

为什么二进制浮点数不能精确表示某些十进制数字

从二进制转十进制的时候的算法就可以看出来

$$2^{-1} + 2^{-2} + ....$$

这样算必然不能表示所有的小数

为什么说浮点数的精度和范围不可兼得

可以从计算公式看出来:总位数固定,小数位越多,指数越少,那么范围就越小,反之亦然。

为什么不要用==比较

精度问题,一些小数不能精确表示,可能看起来相同,比较低的一些二进制位却有差别

if (0.1 + 0.2 == 0.3)

左边本身的表示误差加起来之后误差更大,就可能会不相等。

存储

大小端

指的是二进制如何存储。假设一个存储单位是8bits,直接举例

0x789abc
//假设左边低地址,右边高
//大端,高位存在地址低端
78 9a bc
//小端,低位存低端
bc 9a 78

写代码验证:

typedef unsigned char* pointer;

void show_bytes(pointer start, size_t len) {
    size_t i;
    for (i = 0; i < len; i++)
        printf("%p\t0x%.2x\n", start + i, start[i]); //%.2x 宽度两位
    printf("\n");
}