float、double的精度,在内存中的存储方式
一、浮点型变量在内存中的存储方式
Java的浮点数遵循IEEE 754标准,采用二进制数据的科学计数法来表示浮点数,float遵从的是IEEE R32.24 ,而double 遵从的是R64.53。该标准中表示的浮点数表示分为规约形式和非规约形式以及特殊情况。
无论是单精度还是双精度在存储中都分为三个部分:
-
符号位(Sign) : 0代表正,1代表为负
-
指数位(Exponent):用于存储科学计数法中的指数数据,并且采用移位存储
-
尾数部分(Mantissa):尾数部分
根据IEEE 754 标准,对于float单精度,第 31 位(左边第1位)表示浮点数字的符号;第 30-23位(8位)表示指数(指数加完偏移量,即加偏移量127后的值);第 22-0 位是尾数(尾数是23位);存储方式如下图所示:
指数是有符号的,但并不是使用有符号整形(int)的存储方式,而是使用偏移(Offset)算法,存储的数据=元数据 + 127,所以【实际指数值 = 指数部分二进制值 - 127】。8位二进制能表示的范围为0~255,这样的话范围就是(-127~128),另外全0和全1作为特殊处理,所以指数部分能表示的范围为-126~127(规约形式)。可以参考https://www.zhihu.com/question/21711083
根据IEEE 754 标准,对于double双精度,第 63 位表示浮点数字的符号;第 62-52 位(11位)表示指数(指数加完偏移量);第 51-0 位是尾数(尾数是52位,尾数位比float多,尾数位越多,精度越高);双精度的存储方式为:
指数部分与float单精度存储方式一样使用偏移(Offset)算法,存储的数据=元数据 + 1023,所以【实际指数值 = 指数部分二进制值 - 1023】。11位二进制能表示的范围为0~2047,所以指数部分能表示的范围为-1022~1023。
非规约形式表示: 当指数部分全0而且小数部分不全0时表示的是非规格化的浮点数,因为这里默认没有前导1,而是0。 对于float类型,取值位0.f * 2-126,表示范围位 2-149~(1-2-23) × 2-126 这里没有考虑符号。(IEEE 754标准规定:非规约形式的浮点数的指数偏移值比规约形式的浮点数的指数偏移值小1。) |
其他特殊表示: 1.当指数部分和小数部分全为0时,表示0值,有+0和-0之分(符号位决定),0x00000000表示正0,0x80000000表示负0. 2.指数部分全1,小数部分全0时,表示无穷大,有正无穷和负无穷,0x7f800000表示正无穷,0xff800000表示负无穷. 3.指数部分全1,小数部分不全0时,表示NaN,分为QNaN和SNaN,Java中都是NaN. |
二、二进制的科学计数法
十进制的科学计数法:任何数字都可以表示成a×10n,其中1≤a<10,n表示整数,一般用于表示很大的数字,例如:623500000000可以表示为6.235×1011。同理,任何二进制都可以表示成a×2n,其中a为带小数点的二进制序列,且小数点在第二位,n表示整数,例如:100111101100000000000000可以表示为1.001111011×223。
三、十进制转换为二进制
1.整数部分
余数法:用这个十进制的整数除以 2,会得到一个商值和一个余数值,再用商除以 2,一直除到商为 0 为止,把每次的余数,逆序连起来,就是要转的二进制数。整数总能用二进制准确表示。
2.小数部分
小数部分就是用十进制小数乘以2,得出的积,然后把积的整数位取出,再用积的小数部分乘以2,再把积的整数位取出,再用小数部分乘以2,循环操作,直到小数部分为0,或者遇到无限循环(小数转换为二进制可能会损失精度),取到你认为足够精度的小数为止,然后把取出的整数位顺序连接起来,就是要转换成的二进制小数。
十进制 |
二进制 |
0.5 |
0.1 |
0.25 |
0.01 |
0.125 |
0.001 |
0.0625 |
0.0001 |
0.03125 |
0.00001 |
0.015625 |
0.000001 |
0.0078125 |
0.0000001 |
0.00390625 |
0.00000001 |
十进制 0.875 转换成二进制 0.111,十进制0.3转换成二进制0.01001100110011…。
四、二进制转换为十进制
float与double的二进制表示转换为十进制时,先表示为科学计数法,再分别转换小数部分和整数部分。
0 01111111 00000000000000000000000的指数部分为01111111=(27-1)-127 = 0,尾数部分为00000000000000000000000,(因整数部分总为1,所以存储时省略整数部分)该数的科学计数法表示为1. 00000000000000000000000×20,所以该二进制对应的十进制为1。
1 10000000 00000000000000000000000的指数部分为10000000 = 27-127 = 1,尾数部分为00000000000000000000000,该数的科学计数法表示为1.00000000000000000000000×21,左移1位变为10.0000000000000000000000,所以该二进制对应的十进制为2。
0 01111101 00110011001100110011001的指数部分为01111101 = 125 – 127 = -2,尾数部分为00110011001100110011100,该数的科学计数法表示为1.00110011001100110011100×2-2,右移2位变为0.0100110011001100110011001,整数部分为0,小数部分约为0.3,该数为0.3。
0 10000000 10010010000111111011011的指数部分为10000000 = 27 – 127 = 1,尾数部分为10010010000111111011011,该数的科学计数法表示为1.10010010000111111011011×21,左移1位变为11.0010010000111111011011,整数部分为3,小数部分约为0. 1415926,该数为3.1415926。
五、float、double的精度
数学领域中的精度一般指有效数字,是十进制位数, 而计算机中的精度通常是指二进制位数。
从上述几个float的二进制表示转换为十进制的示例可以看出:
-
指数位决定了范围大小,因为指数位表示的越大则表示的数值越大。
-
尾数位决定了计算精度,因为尾数位能表示的越大,则计算精度越大。
浮点数的精度决定于尾数部分,而float尾数占了23个二进制位,加上省略的整数部分的1,共24位决定浮点数的精度。24位二进制表示的最大数字为224转化为十进制数为 16,777,216(8个十进制位),因此有一种说法是float的十进制精度为8位,但是由于其并不能表示所有8位十进制数,因此也有种说法是其精度为7位。【这些说法都不准确,因为尾数部分包含整数部分和小数部分】准确的说,float可以保证7位十进制有效数字。
float能表示的最大数为0 11111110 11111111111111111111111,也是Float.MAX_VALUE的值,(224-1)× 2(127-23)约为3.4028235E38。
float能表示最小正数为1 00000000 00000000000000000000001(非规约表示),也是Float.MIN_VALUE的值,2-149约为1.4E-45
double位数占52位,加上省略的整数部分的1,共53位决定浮点数的精度。53位二进制表示的最大数字为253转化为十进制数为 9,007,199,254,740,992(16个十进制位),但是它不能表示所有16位十进制数,其精度介于15~16位。准确的说,double可以保证15位十进制有效数字。
Double表示的最大数为1.7976931348623157E308,最小正数为4.9E-324
六、浮点转二进制科学表示的代码实现
1 import java.util.Scanner; 2 import java.util.regex.*; 3 public class FloatToHex { 4 /** 5 *将用户输入的浮点数,转换为二进制科学计数形式(浮点数在内存中的存储方式) 6 *@author: 李世颖 7 *@Create Date: 2020-01-10 8 */ 9 public static void main(String[] args) { 10 // 键盘输入 11 Scanner sc = new Scanner(System.in); 12 String cmd=null; 13 float f = 0; 14 //double d = 0; 15 String binaryStr = ""; 16 int prefixLen = 0;//需要补0位的数量 17 //float类型正则表达式规则 18 Pattern p = Pattern.compile("-?\d+\.?\d*"); 19 Matcher m = null; 20 //提示输入 21 System.out.println("输入浮点数,转换输出该浮点数的内存二进制表示形式。"); 22 while (sc.hasNext()) { 23 //获取输入并删除分隔符 24 cmd = sc.nextLine(); 25 //退出命令 26 if (cmd.equalsIgnoreCase("exit")) { 27 sc.close(); 28 return; 29 } 30 //判断输入是否合法 31 m = p.matcher(cmd); 32 if (m.matches()) { 33 try { 34 f = Float.parseFloat(cmd); 35 //d = Double.parseDouble(cmd); 36 System.out.println(cmd + "在内存中的二进制表示如下:"); 37 binaryStr = Integer.toBinaryString(Float.floatToIntBits(f));//将float转换为二进制字符串 38 //binaryStr = Long.toBinaryString(Double.doubleToLongBits(d)); 39 prefixLen = 32 - binaryStr.length(); 40 //prefixLen = 64 - binaryStr.length(); 41 if(prefixLen > 0){ 42 System.out.println(String.format("%0"+prefixLen+"d",0) + binaryStr);//补0后输出 43 }else{ 44 System.out.println(binaryStr); 45 } 46 } catch (Exception e) { 47 System.out.println("输入的浮点数不合法或超出浮点数的范围"); 48 continue; 49 } 50 } else { 51 System.out.println("请输入合法的浮点数"); 52 } 53 } 54 } 55 }