基本数据类型
本文会举例一些小的边界问题与类型转化问题,引出对底层浅尝辄止的讨论。
字节型与整型的边界问题
byte和整型在计算机中的存储方式是一样的,都是整数的存储。
这里这里我们以byte为例,因为byte的数据范围最小,看起来比较直观。
下面是我写的一个简单程序:
public static void main(String[] args) {
byte maxx = Byte.MAX_VALUE;
byte minn = Byte.MIN_VALUE;
System.out.println(maxx+" "+minn);
++maxx;
--minn;
System.out.println(maxx+" "+minn);
}
运行结果:
maxx代表byte的最大范围,minn代表byte的最小范围。
我们发现,当执行++maxx和- -minn操作后,maxx与minn仿佛发生了置换。为什么会这样?
这是因为计算机中的数据是以 二进制
存储的。
让我们从byte的取值范围 -128~127
入手:
byte是一个字节占8位内存,通常第一位代表符号位(0代表正数,1代表负数)。因此,最大范围为
0111 1111(2)=127(10)。但这样 1000 0000(2)=-0(10) 就没有意义了,为了节省计算机内存,我们约定
1000 0000(2)=-128(10)。
有了上面的知识前提后,回到之前的讨论,maxx与minn仿佛发生了置换的原因就不难想到了:
maxx+1 = 0111 1111 + 1 = 1000 0000
minn-1 = 1000 0000 - 1 = 0111 1111
当然,边界问题还远远没有结束。按照我们一般的逻辑,minn+1的值会是多少呢?
public static void main(String[] args) {
byte minn = Byte.MIN_VALUE;
System.out.println("minn: "+minn);
++minn;
System.out.println("minn: "+minn);
}
是的,-127,完全正确。但别忘了-128的二进制表达形式:1000 0000(2) 也就是说,上述的计算转化为二进制就是:
minn+1 = 1000 0000 + 1 = 1000 0001
-127的二进制表示竟然是1000 0001(2) ,这明显与我们的常识不符,计算机内部的计算方式肯定有什么不同。在这里就要用到补码的知识,这篇博客 写的很不错。
在我阅读这篇博客时,读到全加器的概念,感觉很陌生。如果您也有这样的困惑,建议阅读这篇博客。
有趣的浮点数
浮点数的存储方式
与整型不同,浮点数的存储方式分为三个部分。
IEEE 754规定对于32位的浮点数,最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M。
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
而任意一个二进制浮点数V可以表示为下面的形式:
V
=
(
−
1
)
S
∗
M
∗
2
E
V = (-1) ^S * M * 2^E
V=(−1)S∗M∗2E
注:
- ( − 1 ) S (-1)^S (−1)S表示符号位,当S=0,V为整数,S=1,V为负数。
- M表示有效数字,且
1
<
=
M
<
=
2
1<=M<=2
1<=M<=2但因为计算机内部为二进制存储,为了节约空间,我们可以
默认第一位是1
,而不记录首位的1,只记录小数点后的部分
,当需要将该数取出的时候,才把1加上。因此,真正记录在计算机中的M是 0 < = M < = 1 0<=M<=1 0<=M<=1的。 -
2
E
2^E
2E表示指数位。为了使E能表示负数,E的值在存入计算机时,需加上127(E为8位)或1023(E为11位)。同时,为了能表示一些特殊的事,我们还得分三种情况讨论E:
(1).E不全为0且E不全为1。这是正常情况,需要计算时,将其值减去127或1023即可得到真实值。
(2).E全为0。这时,E等于1-127或1-1023,有效数字M不用加上第一位的1,而是还原为0.xxx的小数。这样做是为了表示正负0,以及非常接近于0的数。
(3).E全为1。这时,如果M全为0,表示正负无穷大(正负取决于符号位s);如果有效数字M不全为0,表示这个数不是一个数(NaN)。
例如:
-5.5 = 1101.1 = (-1)^1 * 1.011 * 2^2
大致是这个意思。
以上概念是我在码农有道里看的一篇文章学的。
[这学期博主刚刚学完计算机组成原理,发现上面讲的很多地方都不太准确,只是原码的表现形式,初学者看着留个印象就好 2019.7.19]
浮点数的精度问题
根据以上所学知识,0.2的浮点数该如何表达呢?首先得把0.2转化为二进制,等等,0.2显然根本就无法转化为二进制(无限循环)。
public static void main(String[] args) {
double a = 0.8;
double b = 1.0;
System.out.println(b-a);
if(b - a == 0.2) {
System.out.println("按理说是这样");
} else {
System.out.println("惊了");
}
}
是的,结果正如我们所想:
java是利用Math
和BigDecimal
的四舍五入函数解决这个问题的。
类型转换
类型转换分为 强制类型转换
和 自动类型转换
两种。
- 自动类型转换:当数据范围小的数据转化为数据范围大的数据时,小数据类型将自动转化为大数据类型。
- 强制类型转换:当数据范围大的数据要变为数据范围小的数据时,必须要使用强制类型转化。
就本人目前的学习而言,只有在算法题中会经常用到类型转换,而在项目中其实反而要避免出现类型转换的情况。
一个简单的例子:
public static void main(String[] args) {
int a = 1;
double b = a; //自动类型转换
System.out.println("a: "+a+" b: "+b);
int c = (int) b; //强制类型转换
System.out.println("b: "+b+" c: "+c);
}