C语言数值存储溢出探讨
真数与码
真数可以等同于我们数学上的整数,包括正整数,0和负整数。
码是什么呢?
码就是为了存储真数而产生的。
计算机的存储空间不是无穷无尽的,如果未来能实现用一个无穷的二进制位来存储一个数,那么码就没有存在的必要了。
码给各个二进制位设定了一套规则,以达到使用这些二进制位来表示真数的目的。常用的有原码,反码,补码。
现在我们计算机中所使用的的大多是补码,下面的内容也根据补码展开。
如果在提及码的时候没有说明是用多少位来存储的,那么就是在耍流氓
补码
最高位表示符号位,剩余的位表示实际的数据。
这里我假设读者已经熟练掌握真数与补码的转化。
如果没熟练的可以看我写的这篇文章更快的进行进制转换
这里再简单补充一下,补码取最高位为符号位,那么在使用打表法
进行快速转换时,最高位的表的值取一个负值即可。例如8位的表取这样的一个序列,-128 64 32 16 8 4 2 1
下面着重介绍当给定的位数无法存储过大或过小的真数(溢出)时,其存储区的变化。
假设我们在用一个8位的存储空间来存储一个数。
其正数最大能存储127,负数最小能存储-128
如果我给出一个真数137,需要存储到这个8位的空间中。
如果我给出一个真数-158,需要存储到这个8位的空间中。
显然,这会产生溢出,那么溢出后,这个存储空间内将会是什么样的内容呢?
为了解决这两个问题(正溢出和负溢出),我们需要熟悉上图的结构。
左边的是其实际的二进制存储空间的内容
中间的一个循环的线性表,不断重复着从127到-128,再到127,再到-128,再到127,再到-128。。。
右边的是我们的真数
未发生溢出时,真数与我们常规的补码转换一致,但是当真数超过所能存储的范围时,就会陷入到这样一个循环中。
知道了这样一个对应关系之后,我们就可以通过取模运算+数数
的方式来计算出发生溢出时,存储空间的实际内容。
所以,经过短暂的计算后可得出
真数137其对应的补码是-119即1000 1001
真数-158其对应的补码是98即0110 0010
下面附上代码验证。
int main()
{
int8_t a = 137;
int8_t b = -158;
printf("a: %0x
", a);
printf("b: %0x
", b);
return 0;
}
执行结果
a: ffffff89
b: 62
0x89: 1000 1001
0x62: 0110 0010
上面所讲到的只是8位的情况,对于32位,64位也遵循同样的规律。
虽然计算机内部使用的是位运算,计算机并不知道真数这样的概念,甚至计算机内部使用的可能是完全的另一套机制,但是上面的这种计算模型总是能够得到正确的结果。如果世上有一种无限接近现实的简单的模型,那么为什么不用呢?