今天,发生一件很是有趣的事情。算法
公司同事问了我一个问题:为何 2.0 - 1.1 = 0.89999999 呢?不该该是 0.9吗?学习
原来是,他问了周围一圈的同事,都给他的是同一个回答,说这是精度问题。他百思不得其解,怎么就会产生精度问题呢。再问,就没人知道缘由了。3d
而后,我就看到了他抱着一本厚厚的书在看。拿过来一看,是一本Java书,厚厚的六百多页,这还仅是第一卷。哟呵,这是准备大干一场啊。code
看在他这么努力学习的份上,还有他那对知识极度渴望的眼神。我决定,把我毕生所学传授与他。blog
因而,就给他详细讲解了,计算机中是怎么存储一个数的,十进制是怎么在转二进制的过程当中丢失精度的,以及浮点数是怎么遵循IEEE 754 规范的,在浮点数进行加减运算的过程当中会经历对阶、移位运算等过程,以及在此过程当中是怎么丢失精度的。(这些问题在以前的文章中都有解答,参看“为何0.1+0.2=0.30000000000000004”)class
而后,成功的把他完全搞懵逼了。怎么这么难啊。test
原来,他的计算机基础比我还匮乏,不知道什么是位运算,不知道什么是原码、反码和补码。基础
本着个人热心肠,我就给他普及了一下这些知识 ---- 负数的补码形式和位移运算。file
咱们知道,一个数分为有符号和无符号。对于,有符号的数来讲,最高位表明符号位,即最高位1表明负数,0表明正数。二进制
在计算机中,存储一个数的时候,都是以补码的形式存储的。而正数和负数的补码表示方式是不同的。正数的补码就等于它的原码,而负数的补码是原码除符号位之外都取反,而后 + 1 得来的。以一个int类型为例(4个字节即32位)
14的原码为:
0000 0000 0000 0000 0000 0000 0000 1110
它的反码、补码和原码都是同样的。
-14的原码为:
//最高位1为符号位,表明此数为负数 1000 0000 0000 0000 0000 0000 0000 1110
反码为原码除了符号位之外的其余位都取反(即0变为1,1变为0),
1111 1111 1111 1111 1111 1111 1111 0001
补码为反码 + 1 ,注意二进制中是满二进一。
1111 1111 1111 1111 1111 1111 1111 0010
位的左移,右移运算就是分别向左和向右移动N位。移位的规则是:
因左移就在右边低位补0就能够了,比较简单,我就以负数的右移来举例,是怎么计算无符号右移和带符号右移的。仍是以 -14 为例。
// -14的补码 1111 1111 1111 1111 1111 1111 1111 0010 // 带符号右移用 >> 表示,即右移一位 -14>>1,高位补符号位1,低位舍去 1111 1111 1111 1111 1111 1111 1111 1001 // 无符号右移用 >>> 表示,即右移一位 -14>>>1,最高位补0 0111 1111 1111 1111 1111 1111 1111 1001
咱们能够经过程序来验证一下 -14>>1和 -14>>>1的结果是否正确。
1. -14>>1 = -7
//咱们算出来 -14>>1的补码为: 1111 1111 1111 1111 1111 1111 1111 1001 //那它具体表明的数值是多少呢? //首先,补码 -1 获得反码 1111 1111 1111 1111 1111 1111 1111 1000 //而后,反码取反获得原码,最高位符号位不变 1000 0000 0000 0000 0000 0000 0000 0111
这结果不就是 -7 吗,而后经过程序计算一下结果:
public class TestMove { public static void main(String[] args) { System.out.println( -14>>1); } }
结果一样也是-7 。说明了咱们位移操做没问题。
2. -14>>>1=2147483641
咱们经过一段程序去验证:
package com.test.binary; /** * @Author zwb * @DATE 2019/12/3 15:49 */ public class TestBinary { public static void main(String[] args) { //咱们本身计算出来的 -14>>>1 结果 String bin = "01111111111111111111111111111001"; double res = binToDec(bin); System.out.println(res); //经过计算机计算的结果 System.out.println(-14>>>1); } //二进制转为十进制 public static double binToDec(String bin){ int index = bin.indexOf("."); int len = bin.length(); double res = 0; //index为-1说明没有小数 if(index == -1){ for(int i = 0; i< len; i++){ res += Math.pow(2,i) * Integer.parseInt(String.valueOf(bin.charAt(len-1-i))); } }else{ //整数部分 int partA = 0; for(int i = 0; i< index; i++){ partA += Math.pow(2,i) * Integer.parseInt(String.valueOf(bin.charAt(index-1-i))); } //小数部分 double partB = 0; for(int j = index + 1; j < len; j++){ partB += Math.pow(2,index - j) * Integer.parseInt(String.valueOf(bin.charAt(j))); } res = partA + partB; } return res; } }
运行以后的结果,能够在控制台打印看到:
上边第一个是咱们本身经过推算它的补码,而后经过二进制转十进制的一个算法算出来的最终结果,第二个就是直接经过位运算算出来的结果。能够看到结果是如出一辙的。
至此,是否是对原码,反码,补码以及位运算左移右移,有了比较清晰的认识了呢?