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 }