前言:本文介绍的整型的溢出并不针对某种编程语言,通过数在机器中存储的方式,说明为什么会存在溢出以及溢出后数的实际存储情况。
一、什么是溢出(理解即可)
当我们在计算机中要存储的数超出了该类型数可以表示的范围就会发生溢出。例如,Java中的byte类型数据范围为[-128,127],你想要存储的数为128,此时会发生上溢;要存储的数为-129,此时会发生下溢。其核心思想是超出可以表示的范围。就像向杯子中倒水,水超出了杯子的容量,就会溢出来。
二、为什么会发生溢出
前文已经有所介绍:超出了可以表示的范围。
数字在计算机中的二进制表示形式。 有两个特点:其一,符号数字化,即”正0负1“。其二,数的大小受机器字长的限制。
计算机中的任何数据都是二进制形式。数字也不例外,在存储进计算机前要对其进行编码。原码、反码和补码都是器编码形式,我们在计算机中存储的机器书采用的是补码。【点击了解详情】
加入使用8位表示一个数,从0000 0000开始,累加1,最大到1111 1111,此时才加1就超出了它可以表示的范围。
三 溢出后的实际结果是多少(重点)
当发生溢出后,计算机实际储存的数是多少是我们更关系的问题。例如,我们会遇到把Java题目:byte b = 128;
b的值为多少
1、从表盘出发![请添加图片描述](https://i-blog.csdnimg.cn/blog_migrate/c2082b1f4291d78e777d3bcb3f76543c.jpeg)
我们不关心表盘上的刻度(因为我可以对刻度重新任意编号),重点关注它可以表示12种状态,把刻度12所在的点称为起点。
从起点出发,顺时针走十步,到达刻度10位置。从起点出发,逆时针走两步,也可以到达刻度10位置。
为了方便后文的介绍,这里引入一个概念:同余。
给定一个正整数m,如果两个整数a和b满足a-b能够被m整除,即(a-b)/m得到一个整数,那么就称整数a与b对模m同余,记作a≡b(mod m)
记作:a≡b (mod m)
记作:a与b对模m同余
性质
反身性:
a ≡ a (mod m) 线性运算定理:如果a ≡ b (mod m),c ≡ d (mod m) 那么:
(1)a ± c ≡ b ± d (mod m)
(2)a * c ≡ b * d (mod m)
如果想看这个定理的证明, 请看:http://baike.baidu.com/view/79282.htm
对于上文提及的顺时针走十步可以理解为正10,逆时针走两步可以理解为负2,10 - (-2) / 12 = 1 或者 (-2 - 10) / 12 = -1,都可以整除,所以-2 ≡ 10(mod 12)。根据上面的定理 0 ≡ 12 (mod12),得出-2 + 0 ≡ 10 + 12(mod12) = -2 ≡ 22(mod12)
继续上面的讨论,由于一共存在十二种状态,当我从起点出发,顺时针(或逆时针)走十二步会到达起点,如果我走的步数大于状态数,会从起点重新出发开始走。步数除以状态数等于走的圈数(商),具体到达位置(余数)。通常我们更关心具体到达的位置,所以用步数取余状态数就可以了。举个例子,我顺时针走了十三步,13(步数) % 12(状态数) = 1,即我最终再次从起点出发(走了多少圈,不是我关心的)走了一步,到达了刻度1位置。
2、整数溢出后的结果:byte b = 128,b真实值为多少?
从最简单的情况开始,我们给表盘标上数值
这里,位置类似于数组的索引,而编号刻度值为索引位置存储的值,这里索引和存储的值恰好一一对应。
位置(索引)范围 [0,12 - 1 = 11]
数据范围[0, 11]
模(状态数): 12
起点:0
顺时针走
0,从起点0走0步,到达刻度0;
1,从起点0走1步,到达刻度1;
2,从起点0走2步,到达刻度2;
…
11,从起点0走11步,到达刻度11;
12,从起点0走12步,到达刻度0;
13,从起点0走13步,到达刻度1;
…
逆时针走
0,从起点0走0步,到达刻度0;
-1,从起点0走1步,到达刻度11;
-2,从起点0走2步,到达刻度10;
…
-11,从起点0走11步,到达刻度1;
-12,从起点0走12步,到达刻度0;
-13,从起点0走13步,到达刻度11;
通过观察可以知道:
可以看出,以0作为起点,要表示的数在数据范围[1,11]时和刻度值一一对应。
当正数大于等于12时,直接取余。例如,13 % 12 = 1,13对应的刻度值是1。
-1 对应的刻度值为11,-2对应的刻度值值为10,而-1 ≡ 11 (mod12),-2 ≡ 10 (mod12)。
当负数的绝对值小于12时,负数对应的刻度值就是在位置范围内的同余的正数
当负数的绝对值大于等于12时,先取余,再求在数据范围内的同余的正数。例如 -13 % 12 = -1 ,-1 ≡ 11 (mod12) ,-13对应的刻度值是11。
考虑一下从1开始作为起点进行编码会怎么样呢?
位置(索引)范围 [0,12 - 1 = 11]
数据范围[0, 11]
模(状态数): 12
起点:1
本质还是没有改变,从起点走一步来到了下一步的位置,只不过下一步位置的刻度从之前的1变成了现在都2。这里的位置像是数组里的索引,而刻度值(即编码)像是数组里存储的值,之前数组里存储的刻度值和”索引“保持一致,现在,整体上存储的刻度值比索引增加一。我们之前计算的时候,就不关注刻度值 ,而是关注位置(即索引),然后取出位置中存储的值即可。
之前,只是恰好,存储的值和索引值保持一致而已。现在只是整体偏移量1(即起点从0,偏移为1)。我们依然按照上面的方法计算,每次计算出结果加上起点偏移即可。例如:
-1 不在数据范围内,但是绝对值是小于模12的,又 -1 ≡ 11 (mod12),有偏移1, 11 + 1= 12。
所以起点为1所在位置,-1表示的数是12。
最重要的来了:
上图是java的byte类型可以表示的数据范围,仅仅标注了部分刻度值或编号。
位置(索引)范围 [0,255]
数据范围[-128, 127]
模(状态数): 256
起点:0
当要表示的数为128时,从0开始走128步,来到了索引128,其中存储的数为-128,
因此byte b = 128,变量实际存储的值是-128
如果要表示的数等于或者超过了模256,很简单,取余后就是结果。例如257 % 256 = 1,从0开始走了1步,来到了索引1,其中存储的值也为1。
也许你会有新的问题,我没有这个“表盘”,如何知道索引128中存储的值是多少,有没有通用的计算方法?
答案是肯定的。
还记得我们之前提到的同余概念嘛,
模为12的表盘,顺时针走8步,等于逆时针走4步。8 ≡ -4 (mod12)
模为256的表盘,顺时针走128步,等于逆时针走128步 128 = -128(mod 256);
出个题检测一下,a和b实际存储的值是多少呢?(注,在java中实际代码为byte a = (byte)520 )
byte a = -666;
byte b = 888;
答案写在评论区吧:)