背景
最近在看到Integer的代码,有点不解,为何这样就能对数字进行反转,决心一探究竟
public static int reverse(int i) {
// HD, Figure 7-1
i = (i & 0x55555555) << 1 | (i >>> 1) & 0x55555555;
i = (i & 0x33333333) << 2 | (i >>> 2) & 0x33333333;
i = (i & 0x0f0f0f0f) << 4 | (i >>> 4) & 0x0f0f0f0f;
i = (i << 24) | ((i & 0xff00) << 8) |
((i >>> 8) & 0xff00) | (i >>> 24);
return i;
}
突然发现大学学的《数字逻辑》相关的知识几乎已经全部还给老师了,为了弄懂上述问题,决定先把二进制的基础在补回来。
计数制
常用进位计数制
数制转换
二进制和十六进制转换成十进制
假设我们有一个二进制数 1101.1 十六进制数 2F0.C 需要转换成二进制数
二进制转换成十进制
十六进制转换成十进制
读者可以自己出题测试,可以在https://tool.ip138.com/hexconvert/ 进行验证(网上有很多进制转换工具是不支持小数转换的,大家在用工具验证的时候需要注意)
二进制、八进制和十六进制之间的相互转换
随机敲个二进制数:11010011101.101 ,如何将他们快速的转换成八进制和十六进制呢?我们知道8是$ 2^3 $ 在二进制中占3位(bit),16是 2 4 2^4 24 在二进制中占4位,因此我们可以做如下转换
初始值
11010011101.101
11010011101.101
11010011101.101
八进制转换
十六进制转换
由上得出结论11010011101.101转成八进制为3235.5 转成16进制为69d.a,二进制、四进制、八进制、十六进制、三十二进制等,都可以根据该方法进行相互转换
十进制转成二进制
十进制转成二进制需要分成整数部分和小数部分,整数部分采用“除2取余法”,小数部分采用“乘2取整法”
现在我们来看个例子,将18.188转换成二进制数
-
整数转换,采用“除2取余”法,对整数部分依次除2,直到结果为0时,取余数部分,由下至上得到结果10010
-
小数转换,采用“乘2取整”法,对小数部分乘2,取整数部分的值,直到小数部分为0。如果我们只保留小数后四位,则由上至下得到结果0011,如果要得到精确值就必须一直往下乘2 直到小数部分为0,需要注意的是下列计算我们只针对小数部分乘2,整数部分的值就是要的结果
由上可得出结论,如果二进制结果只保留小数后四位则得出结果为10010.0011
原码、补码、反码
上面谈到进位计数制,没有涉及到二进制的符号表示,在实际使用过程中需要用符号来表示负数(-)和正数(+)。实际计算机采用的是补码的形式来表示正(+)负(-)数,咱们先了解下原码,反码、补码三种形式的区别,再进一步探究为什么计算机采用的是补码。
基本概念
计算机的最小单位是byte(字节),一个字节占8位。咱们以5和-5为例,来探究一下如果以原码,反码,补码在计算机中如何存储。
-
原码:最高位表示数的符号,其他未表示数值,0代表正数,1代表负数
例如:[+5]原 = 00000101 [-5]原 = 10000101
-
反码:
正数的反码和原码相同 [+5]反 = 00000101 =[+5]原
负数的反码是由原码的符号位不变,其余位按位取反 [-5]反=11111010
-
补码
正数的反码和原码相同 [+5]补 = 00000101 =[+5]原
负数的补码是由其原码的符号位不变,其与位按位取反,在最低位加1,[-5]补=11111011
为何计算机使用的是补码
上面咱们了解了原码、反码、补码的区别,对于人们而言,原码是最易于理解且易于计算的,为何还要有反码和补码?
这是因为计算机的数字电路中只有"加法器",咱们都知道1-1可以用1+(-1)来实现,CPU厂商没必要专门再设计一种“减法器”。
接下来咱们通过1+(-1)这个例子来说明为什么要有反码和补码
1、原码计算
很显然,如果计算机采用的是原码进行计算,得出的是错误的结果,因此有了反码进行计算
2、反码计算
可以看到如果根据反码进行计算能得到正确结果,但是我们在计算过程中会发现存在一个问题,存在[0000 0000]原和[1000 0000]原两个编码表示0,这样虽然正确,但是却没有意义。因此有了补码
3、补码计算
原文链接:技术人员应该知道的二进制基础
欢迎关注:码头码农