二进制
十进制小数转二进制小数
如 十进制 0.75
0.75 * 2 = 1.5 ... 取整 1
0.5 * 2 = 1.0 ... 取整 1
所以得出二进制小数 0.11
二进制小数转十进制小数
如 二进制 0.11
1 * 0.5(2的-1次方) = 0.5
1 * 0.25(2的-2次方)= 0.25
所以相加得出十进制小数 0.75
说明:小数点后第一位是-1次方,下一位是-2次方,依次类推
补数
补数可以简单对应到十进制中的相反数,如 10与-10互为相反数;
而在二进制中就是用正数来表示的负数,可能这话不太好理解
在二进制表示负数值时,一般会把最高位作为符号来使用,0:表示正数,1:表示负数
看个例子
十进制数1 对应的二进制数是 00000001
十进制数-1 对应的二进制数是 ?
再算十进制数1的相反数-1,在二进制数中表示,其实就是算 00000001 的补数
补数的公式 :每一位数取反 ,然后再 + 1,加1的时候需要考虑进位问题,等同于十进制中满10进的1原则
00000001 取反 11111110 ,加1 ,得出补数 11111111
我们可以反向验证下
00000001 + 11111111 = 0
00000001
+ 11111111 (这里该进位的要进位)
——————————
00000000
在计算机中减法的实现其实就是通过补数的方式实现的,
比如1-1 = 0 => 1 + (-1) = 0 ,计算时全部转成二进制计算
移位运算
左移
正数的移位基本和十进制是一样的,左移一位是1x10,计算机就是1x2,所以左移就是2的n次方;
左移低位都是补0;
负数的左移,如 -14 << 2 = -56
在计算机遇到负数,都是采用补数的方式-14的相反数是14
二进制14 00001110
二进制14的补数-14 11110001 +1 => 11110010
二进制-14左移两位 11001000
二进制-14的补数 00110111 +1 => 00111000
十进制得 -(32+16+8) =-56
右移
正数十进制右移一位是1x0.1,计算机就是1x0.5(2的-1次方),所以右移就是2的n次方分之一;
右移正数高位都是补0;
当负数时,若该数值是用补数表示的负数值,则右移后的所有高位补1
负数的右移,如 -14 >> 2 = -4
二进制14 00001110
二进制14的补数-14 11110001 +1 => 11110010
二进制-14右移两位 11111100
二进制-14的补数 00000011 +1 => 00000100
十进制得 -(4)
浮点数
计算机中浮点数表示法
在计算机中表示小数是采用浮点数表示法,所以像1011.0011这样的小数形式,完全都是在纸上运算的结果。
浮点数是指用符号、尾数、基数和指数这四部分来表示的小数.其规定小数点前面的值固定为1的正则表达式.
IEEE规定的浮点数
(+-)m*n^e
+-:符号、
m :尾数、
n :基数、
e :指数、
十进制数0.5,用浮点数表示
0.5 * 10^0
二进制数0.11,用浮点数表示
1.1*2^-1
尾数部分:用的是将小数点前面的值固定为1的正则表达式;
指数部分:用的是EXCESS系统表现法,是指根据指数部分的最大值的一半,得出中间值,记作0,使得负数不需要用符号来表示(不知道为什么这样设计);
比如指数部分8位 ,011111110 -> 126
用 EXCESS系统表现就是 126 - 127 = -1 ,因为255的一半127 是0
计算机中计算小数的时候其实都是按照下面存储计算的,并非采用IEEE规定的浮点数表示法,所以我认为EXCESS系统表现方式就是为了方便与浮点数表示法做互相转化的.
程序中常用的浮点数double与float内部构造(IEEE-754规定)
双精度浮点数(64位):
1位 11位 52位
<--------><----------><--------------------->
符号部分 指数部分 尾数部分
单精度浮点数(32位):
1位 8位 23位
<--------><----------><--------------------->
符号部分 指数部分 尾数部分
从IEEE的规定的构造中得出,
双精度的有效尾数是52位,另加1位(该位即小数点前的1,但是实际内存中不会存储,这是约定),即53位,2^53,对应到十进制的 10^15 ~ 10^16,所以在十进制中有效位是15位
而单精度的有效尾数是23位,另加1位,即24位,224,对应到十进制的107 ~ 10^8,所以十进制中有效位是7位
摘自书里,直接看这个可能会清晰点
float a=0.75的二进制
0-01111110-10000000000000000000000
其中,
符号位0,表示是正数;1,表示负数
指数部分01111110,转化为十进制是126,EXCESS系统表现为126-127=-1;
尾数部分10000000000000000000000,表示二进制数1.10000000000000000000000(尾数部分表示的是了1.以后的部分);
所以,
用二进制数表示为1.1x2^-1,用十进制表示位1.5x2^-1 = 0.75;
程序计算小数出错的原因
综上所述,计算机是无法处理无限循环的小数,因此,在遇到循环小数时,计算机就会根据变量数据类型所对应的长度将数值从中间截断或四舍五入。比如说float 类型的0.1,在转成二进制数时就是无限循环的,所以计算机会根据float的尾数部分在23位处截取,舍弃后面的部分。
这里再举一个例子
再按上面所说的,再计算下 float a = 6.7 在计算机内存中是如何存储的
1、6 对应的二进制是00000110
0.7对应的二进制是10110011001100110011001...
0.7 * 2 =1.4 1
0.4 * 2 =0.8 0
0.8 * 2 =1.6 1
0.6 * 2 =1.2 1
0.2 * 2 =0.4 0
0.4 * 2 =0.8 0
0.8 * 2 =1.6 1
0.6 * 2 =1.2 1
后面就一直循环...
所以6.7的二进制就是110.10110011001100110011001(保留有效位),
2、这样的数只是我们书面的表达方式,所以需要进一步转化,需要先左移两位,将小数点移到有效数之后(或者说是第一个1之后),得出(1.1010110011001100110011001 * 2的2次方)
3、接着就是把得出得数单精度的浮点数表示法
符号部分:0
尾数部分:1010110011001100110011(23位)
指数部分:左移两位,2 + 127 = 129 =>10000001
所以最后内存存储的结果是 0 - 10000001 - 1010110011001100110011
如何防止精度丢失
回避策略
即10公里差1厘米,这样的误差可以忽略不计
把小数转换成正数来计算
就是把小数x10、x100先扩大计算,再缩小;
可以简单看看Java中BigDecimal的实现
见下图
从图中看到BigDecimal的就是通过扩大小数的倍数来计算的;
采用BCD方法
https://en.wikipedia.org/wiki/Binary-coded_decimal
总结记录结束!!!