之前我們學過了Java的四種基本整數類型:
-
byte(1字節)
-
short(2字節)
-
int(4字節)
-
long(8字節)
其中一個字節是8位,所以能表示的個數就是28*x個(其中x表示字節數)
因為有正數和負數,所以范圍就是-28*x-1到28*x-1-1,正數為什么要減一呢,因為有個0算正整數。
可能看起來不太簡潔但意思就是這個意思
而浮點數分別為float(4字節)和double(8字節)
也就是32位和64位
這在計算機中要怎么表示呢
無論是單精度還是雙精度在存儲中都分為三個部分:
-
符號位(Sign) :0代表正,1代表為負
-
指數位(Exponent):用於存儲科學計數法中的指數數據,並且采用移位存儲
-
尾數部分(Mantissa):尾數部分
首先我們知道常用科學計數法是將所有的數字轉換成(±)a.b x 10c 的形式,其中a的范圍是1到9共9個整數,b是小數點后的所有數字,c是10的指數。
而計算機中存儲的都是二進制數據,所以float存儲的數字都要先轉化成(±)a.bx2c,由於二進制中最大的數字就是1,所以表示法可以寫成(±)1.b x 2c的形式,float要想存儲小數就只需要存儲(±),b和c就可以了。
float的存儲正是將4字節32位划分為了3部分來分別存儲正負號,小數部分和指數部分的:
- Sign(1位):用來表示浮點數是正數還是負數,0表示正數,1表示負數。
-
-
Mantissa(23位):尾數部分。也就是上文中提到的數字b。
三部分在內存中的分布如下,用首字母代替類型
S | E | E | E | E | E | E | E | E | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 |
float存儲示例
以數字6.5為例,看一下這個數字是怎么存儲在float變量中的:
先來看整數部分,模2求余可以得到二進制表示為110。
再來看小數部分,乘2取整可以得到二進制表示為.1
(小數轉換二進制方法:乘2取整法,即將小數部分乘以2,然后取整數部分,剩下的小數部分繼續乘以2,然后取整數部分,剩下的小數部分又乘以2,一直取到小數部分為零為止。如果永遠不能為零,就同十進制數的四舍五入一樣,按照要求保留多少位小數時,就根據后面一位是0還是1,取舍,如果是零,舍掉,如果是1,向入一位。換句話說就是0舍1入。讀數要從前面的整數讀到后面的整數。
例:將0.375換算為二進制
得出結果:將0.375換算為二進制(0.011)2
分析:第一步,將0.375乘以2,得0.75,則整數部分為0,小數部分為0.75;
第二步, 將小數部分0.75乘以2,得1.5,則整數部分為1,小數部分為0.5;
第三步, 將小數部分0.5乘以2,得1.0,則整數部分為1,小數部分為0.0;
第四步,讀數,從第一位讀起,讀出每一次的整數部分,讀到最后一位,即為0.011。)
拼接在一起得到110.1然后寫成類似於科學計數法的樣子,得到1.101 x 22。
從上面的公式中可以知道符號為正,尾數是101,指數是2。
符號為正,那么第一位填0,指數是2,加上偏移量127等於129,二進制表示為10000001,填到2-9位,剩下的尾數101填到尾數位上即可
S | E | E | E | E | E | E | E | E | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
內存中二進制數01000000 11010000 00000000 00000000表示的就是浮點數6.5
float范圍
明白了上面的原理就可求float類型的范圍了,找到所能表示的最大值,然后將符號為置為1變成負數就是最小值,要想表示的值最大肯定是尾數最大並且指數最大,那么可以得到尾數為 0.1111 1111 1111 1111 1111 111,指數為 1111 1111,但是指數全為1時有其特殊用途,所以指數最大為 11111110,指數減去127得到127,所以最大的數字就是1.1111111 1111111 11111111 x 2127,這個值為 340282346638528859811704183484516925440,通常表示成3.4028235E38 ,那么float的范圍就出來了:
[-3.4028235E38, 3.4028235E38]
float精度
float 類型的數據精度取決於尾數,相信大家都知道這一點,但是精度怎么算我也是迷糊了好久,最近在不斷嘗試的過程中漸漸的明白了,首先是在不考慮指數的情況下23位尾數能表示的范圍是[0, 223− 1],實際上尾數位前面還隱含了一個"1",所以應該是一共24位數字,所能表示的范圍是[0, 224-1](因為隱含位默認是"1",所以表示的數最小是1不是0,但是先不考慮0,后面會特殊介紹,這里只按一般值計算),看到這里我們知道這24位能表示的最大數字為 224-1,換算成10進制就是16777215,那么[0, 16777215]都是能精確表示的,因為他們都能寫成1.b x2c的形式,只要配合調整指數c就可以了。
16777215 這個數字可以寫成1.1111111 11111111 1111111 * 223,所以這個數可以精確表示,然后考慮更大的數16777216,因為正好是2的整數次冪,可以表示1.0000000 00000000 00000000 * 224,所以這個數也可以精確表示,在考慮更大的數字16777217,這個數字如果寫成上面的表示方法應該是 1.0000000 00000000 00000000 1 * 224,但是這時你會發現,小數點后尾數位已經是24位了,23位的存儲空間已經無法精確存儲,這時浮點數的精度問題也就是出現了。
看到這里發現 16777216 貌似是一個邊界,超過這個數的數字開始不能精確表示了,那是不是所有大於16777216的數字都不能精確表示了呢?其實不是的,比如數字 33554432 就可以就可以精確表示成1.0000000 00000000 00000000 * 225,說道這里結合上面提到的float的內存表示方式,我們可以得出大於 16777216 的數字(不超上限),只要可以表示成小於24個2的n次冪相加,並且每個n之間的差值小於24就能夠精確表示。換句話來說所有大於 16777216 的合理數字,都是[0, 16777215]范圍內的精確數字通過乘以得到的,同理所有小於1的正數,也都是 [0, 16777215] 范圍內的精確數字通過乘以得到的,只不過n取負數就可以了。
16777216 已經被證實是一個邊界,小於這個數的整數都可以精確表示,表示成科學技術法就是1.6777216 *107 ,從這里可以看出一共8位有效數字,由於最高位最大為1不能保證所有情況,所以最少能保證7位有效數字是准確的,這也就是常說float類型數據的精度。
float小數
從上面的分析我們已經知道,float可表示超過16777216范圍的數字是跳躍的,同時float所能表示的小數也都是跳躍的,這些小數也必須能寫成2的n次冪相加才可以,比如0.5、0.25、0.125…以及這些數字的和,像5.2這樣的數字使用float類型是沒辦法精確存儲的,5.2的二進制表示為101.0011001100110011001100110011……最后的0011無限循環下去,但是float最多能存儲23位尾數,那么計算機存儲的5.2應該是101.001100110011001100110,也就是數字 5.19999980926513671875,計算機使用這個最接近5.2的數來表示5.2。關於小數的精度與剛才的分析是一致的,當第8位有效數字發生變化時,float可能已經無法察覺到這種變化了。
float特殊值
我們知道float存儲浮點數的形式是(±)1.b x 2c ,因為尾數位前面一直是個1,所以無論b和c取什么樣的值,都無法得到0,所以在float的表示方法中有一些特殊的約定,用來表示0已經其他的情況。
float的內存表示指數位數有8位,范圍是[0, 255],考慮偏移量實際的指數范圍是[-127,128],但實際情況下指數位表示一般數字時不允許同時取0或者同時取1,也就是指數位的實際范圍是[-126,127],而指數取-127和128時有其特殊含義,具體看下面表格:
符號位 | 指數位 | 尾數位 | 數值 | 含義 |
---|---|---|---|---|
0 | 全為0 | 全為0 | +0 | 正數0 |
1 | 全為0 | 全為0 | -0 | 負數0 |
0 | 全為0 | 任意取值f | 0.f∗2-126 | 非標准值,尾數前改為0,提高了精度 |
1 | 全為0 | 任意取值f | 0.f∗2-126 | 非標准值,尾數前改為0,提高了精度 |
0 | 全為1 | 全為0 | +Infinity | 正無窮大 |
1 | 全為1 | 全為0 | -Infinity | 負無窮大 |
0/1 | 全為1 | 不全為0 | NaN | 非數字,用來表示一些特殊情況 |
總結
-
float的精度是保證至少7位有效數字是准確的
-
float的取值范圍[-3.4028235E38, 3.4028235E38],精確范圍是[-340282346638528859811704183484516925440, 340282346638528859811704183484516925440]
-
一個簡單的測試float精度方法,C++代碼中將數字賦值給float變量,如果給出警告warning C4305: “=”: 從“int”到“float”截斷,則超出了float的精度范圍,在我的測試中賦值為16777216及以下整數沒有警告,賦值為16777217時給出了警告。
PS:引用了一些csdn的,覺得有用就點個贊吧!