您好,登錄后才能下訂單哦!
二更,因為好幾個人因為這篇文章把我批斗了,把有問題的地方修正。
今天看到一個問題
能不能用 double 去取代 float ?
前段時間,有個朋友問我,在 java 里面我想把一個高精度的浮點型存儲下來,但是每次存儲的時候都會被強制降低精度,對于浮點型的理解,真的非常非常重要,特別對嵌入式軟件開發,或者算法開發,涉及到數據類的,浮點型非常非常關鍵,就比如,微信為什么不讓我發 0.0000000001 元的微信紅包呢? 有沒有想過這個問題?如果這樣做對于他們服務器后臺的運算能力要求非常高, so , ~~~~~
所以想簡單寫一下, float 和 double 的區別以及應用場景
<img src="https://pic3.zhimg.com/v2-19c15ba72548ffb535861e5a5625b65a_b.jpg" data-caption="" data-size="normal" data-rawwidth="281" data-rawheight="25" class="content_image" width="281">
1 、浮點型的值范圍
float 和 double 的范圍是由指數的位數來決定的。在 VC++6.0 中, float 占 4 個字節, double 占 8 個字節。
Type Storage size Value range
float 4 byte 1.2E-38 to 3.4E+38
double 8 byte 2.3E-308 to 1.7E+308
long double 10 byte 3.4E-4932 to 1.1E+4932
2 、浮點型在內存里面是如何存儲的?
我相信這個問題大家沒有好好的去考慮過,浮點型如何存儲,這才是我們討論浮點型的關鍵所在,關于上面的浮點型取值范圍,也是網上拷貝下來的,至于真實性,我覺得你們要看了這部分才可能真正理解浮點型,而且最好在自己的編譯器去測試,浮點型是可以等于負數的,所以上面的范圍,你們認為是正確的嗎?
<img src="https://pic2.zhimg.com/v2-9d554740d41074c27e49febf5448c86d_b.jpg" data-caption="" data-size="normal" data-rawwidth="300" data-rawheight="300" class="content_image" width="300">
我拿 float 來舉個栗子:
<img src="https://pic4.zhimg.com/v2-8a912b5c1343b1223ea36b94538dd67f_b.jpg" data-size="normal" data-rawwidth="637" data-rawheight="191" class="origin_image zh-lightbox-thumb" width="637" data-original="https://pic4.zhimg.com/v2-8a912b5c1343b1223ea36b94538dd67f_r.jpg">
float 在內存中的存儲方式
以下 部分如發現問題,請留言,會發小小紅包感謝,微信 weiqifa0
首先使用基數 2 而不是基數 10 來表示科學計數法變體中的浮點值。例如,值 3.14159 可以表示為
1.570795 * 2^{1}
1.570795 是 有效數字又名尾數(在上圖中指尾數部分) ; 它是包含有效數字的數字的一部分。此值乘以基數 2 ,上升到 1 的冪,得到 3.14159 。
浮點數通過存儲 有效數和指數(以及符號位) 進行編碼。
典型的 32 位布局如下所示:
3 32222222 22211111111110000000000
1 09876543 21098765432109876543210
+-+--------+-----------------------+
| | | |
+-+--------+-----------------------+
^ ^ ^
| | |
| | +-- 有效數
| |
| +------------------- 指數
|
+------------------------ 符號位
與有符號整數類型一樣,高位表示符號 ; 表示 正 值, 1 表示 負 值。
而對于指數部分,因為指數可正可負, 8 位的指數位能表示的指數范圍就應該為 :-127-128 了, 所以指數部分的存儲采用移位存儲, 存儲的數據為元數據 +127 。
舉例:
<img src="https://pic1.zhimg.com/v2-0df447013f853a47b89d51dd0d88a288_b.jpg" data-caption="" data-size="normal" data-rawwidth="191" data-rawheight="98" class="content_image" width="191">
剩余的比特用于有效數字。每個位表示從左側算起的 2 的負冪, float 一共 23 位 ,舉例:
<img src="https://pic2.zhimg.com/v2-f5a935ed3397d4b35c3592e0059f492d_b.jpg" data-caption="" data-size="normal" data-rawwidth="625" data-rawheight="81" class="origin_image zh-lightbox-thumb" width="625" data-original="https://pic2.zhimg.com/v2-f5a935ed3397d4b35c3592e0059f492d_r.jpg">
某些平臺假定有效數中的 “ 隱藏 ” 前導位始終設置為 1 ,因此有效數中的值始終在 [0.5,1 之間 ] 。這允許這些平臺以稍高的精度存儲值。
所以 3.14159 的值將表示為
0 10000000 10010010000111111010000
^ ^ ^
| | |
| | + --- 有效數 = 1.570795 ...
| |
| + ------------------- 指數 = 2 ( 130 - 128 )
|
+ ------------------------- sign = 0 (正面)
value = 1 (符號位) * 2 (指數位) * (有效數字)
值 = 1 0 * 2^1 * 1.570795 ...
值 = 3.14159 ......
現在,如果你將有效數字中的所有位相加,你會注意到它們總共 不是 3.14195 ;
他們實際上 是 3.141590118408203125 ,(小編實測數據) 。
沒有足夠的位來準確存儲值 ; 我們只能存儲一個近似值。有效數字中的位數決定了精度 23 位給出了大約 6 位精度的十進制數字 。 64 位浮點類型在有效位數中提供足夠的位, 可提供大約 12 到 15 位的精度 。但要注意,有一些數值不能被精確表示。就像 1/3 這樣的值不能用有限數量的十進制數字表示,由于值是近似值,因此使用它們進行計算也是近似值,并且累積誤差會累積。
<img src="https://pic2.zhimg.com/v2-9d554740d41074c27e49febf5448c86d_b.jpg" data-caption="" data-size="normal" data-rawwidth="300" data-rawheight="300" class="content_image" width="300">
#include <stdio.h>
void main ( void )
{
for ( float i = ; i < 1 ;i += 0.01 )
{
printf( "%f \r\n" ,i);
}
for ( double i = ; i < 1 ;i += 0.01 )
{
printf( "%f\r\n" ,i);
}
for ( long double i = ; i < 1 ;i += 0.01 )
{
printf( "%Lf\r\n" ,i);
}
}
注意其中輸出
<img src="https://pic2.zhimg.com/v2-6a46dbbc43ef82e569ea483166516b61_b.jpg" data-size="normal" data-rawwidth="161" data-rawheight="238" class="content_image" width="161">
到 0.830000 之后明顯出現了誤差
<img src="https://pic3.zhimg.com/v2-19c15ba72548ffb535861e5a5625b65a_b.jpg" data-caption="" data-size="normal" data-rawwidth="281" data-rawheight="25" class="content_image" width="281">
3 、反向驗證第二點的存儲推斷
上面的計算,我們可以通過一個小代碼反向驗證,代碼如下
#include <stdio.h>
int main ()
{
/*0b1000000010010010000111111010000*/
float num = 3.14159f ;
int * p = ( int * ) & num;
printf( "0x%x\n" , * p);
return ;
}
<img src="https://pic3.zhimg.com/v2-74b26980679454ee8f3862b9fa9813d6_b.jpg" data-size="normal" data-rawwidth="617" data-rawheight="40" class="origin_image zh-lightbox-thumb" width="617" data-original="https://pic3.zhimg.com/v2-74b26980679454ee8f3862b9fa9813d6_r.jpg">
輸出 16 進制數據
so~~~~
3.14159 ≈ 0x40490fd0 = 0 10000000 10010010000111111010000
<img src="https://pic4.zhimg.com/v2-4bda4f506f20dbb3baa4096af2d960a7_b.jpg" data-size="normal" data-rawwidth="573" data-rawheight="230" class="origin_image zh-lightbox-thumb" width="573" data-original="https://pic4.zhimg.com/v2-4bda4f506f20dbb3baa4096af2d960a7_r.jpg">
16 進制對應二進制數據
對于 double 和 long double 的大小和精度可以通過這個方式來驗證。
<img src="https://pic3.zhimg.com/v2-19c15ba72548ffb535861e5a5625b65a_b.jpg" data-caption="" data-size="normal" data-rawwidth="281" data-rawheight="25" class="content_image" width="281">
4 、浮點型 printf 輸出格式
printf 輸出范圍 %f %g %Lf %e
#include <stdio.h>
void main ( void )
{
float f_sum = ;
double d_test = ;
f_sum = - 3.4 * 10e-38 ;
d_test = - 1.7 * 10e-308 ;
printf( "%.38f\r\n" ,f_sum);
printf( "%.308f\r\n" ,d_test);
printf( "%g\r\n" ,f_sum);
printf( "%g\r\n" ,d_test);
f_sum = 3.4 * 10e37 ;
d_test = 1.7 * 10e307 ;
printf( "%g\r\n" ,f_sum);
printf( "%g\r\n" ,d_test);
}
輸出如下
weiqifa@ubuntu:~/c/float$ gcc float.c && a.out
-0.00000000000000000000000000000000000034
-0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017
-3.4e-37
-1.7e-307
3.4e+38
1.7e+308
weiqifa@ubuntu:~/c/float$
<img src="https://pic3.zhimg.com/v2-19c15ba72548ffb535861e5a5625b65a_b.jpg" data-caption="" data-size="normal" data-rawwidth="281" data-rawheight="25" class="content_image" width="281">
5 、 精度問題
浮點數在內存中是按科學計數法來存儲的,其整數部分始終是一個隱含著的 "1" ,由于它是不變的,故不能對精度造成影響。
float : 2^23 = 8388608 ,一共七位,這意味著最多能有 7 位有效數字,但絕對能保證的為 6 位,也即 float 的精度為 6~7 位有效數字;
double : 2^52 = 4503599627370496 ,一共 16 位,同理, double 的精度為 15~16 位。
<img src="https://pic2.zhimg.com/v2-9d554740d41074c27e49febf5448c86d_b.jpg" data-caption="" data-size="normal" data-rawwidth="300" data-rawheight="300" class="content_image" width="300">
小代碼舉例
#include "stdio.h"
int main ( void )
{
float fa = 1.0f ;
float fb = - 2.123456789f ;
float fc = 3.999999f ;
double da = 1.0 ;
double db = - 4.0000000 ;
double dc = 3.123456789012345 ;
printf( "%-32.32f \r\n%-32.32f \r\n%-32.32f\r\n" ,fa,fb,fc);
printf( "%-64.64f \r\n%-64.64f \r\n%-64.64f\r\n" ,da,db,dc);
return ;
}
輸出
1.00000000000000000000000000000000
-2.12345671653747558593750000000000
3.99999904632568359375000000000000
1.0000000000000000000000000000000000000000000000000000000000000000
-4.0000000000000000000000000000000000000000000000000000000000000000
3.1234567890123448030692543397890403866767883300781250000000000000
<img src="https://pic3.zhimg.com/v2-19c15ba72548ffb535861e5a5625b65a_b.jpg" data-caption="" data-size="normal" data-rawwidth="281" data-rawheight="25" class="content_image" width="281">
6 、浮點值和 “0”
不知道大家對精度是怎么看的,理論上浮點是永遠不可能等于 “0” 的,只能無盡接近于 “0” ,所以你拿浮點型 和 “0” 比較 ,千萬千萬不要用 “== ” 恒等于符號,而是用大小于符號,精度越大,說明越無盡接近于 “0” ,有時候 float 的精度容易引起問題,看下面的例子。 --- 之前寫的,下面論證是否正確
評論已經有人說,浮點值肯定可以等于 1 ,這個不再做論證,現在論證浮點值和 值,是不是相等的。
所以,我做了實驗,我的文章不一定保證正確,但是提出的觀點一定要有論證的根據
晚上回來很累,跟楠哥睡了一下, 10 點的時候,楠哥又起來了,我也想更新下評論的問題,我測試的代碼如下,里面有注釋。
#include "stdio.h"
int main()
{
/*0b01000000010010010000111111010000 3.14159 的二進制 */
/*0b00111111100000000000000000000000 1 的二進制 */
//int it = 0b01000000010010010000111111010000;
int it = 0b00111111100000000000000000000000;
float *num = (float*)⁢
float num1;
int *p = (int *)&num1;
printf("%f\r\n",*num);/* 輸出我們認為是 0 的二進制數值 */
printf("%f\r\n",num1);/* 未初始化的 float 值 */
printf("0x%x\r\n",*p);/* 打印里面的內容 */
/* 里面的內容是 0x401980 */
/* 轉成 二進制是 0b 0100 0000 0001 1001 1000 0000*/
/* 這樣好看點 0b 0 10000000 001100110000000*/
int it2 = 0x401980;
float *num3 = (float *)&it2;
/* 分別用三種方式輸出 */
printf("%f\r\n",*num3);
printf("%-32.32f\r\n",*num3);
printf("%d\r\n",(int)*num3);
return 0;
}
輸出如下圖
<img src="https://pic1.zhimg.com/v2-92a07e5e1560c1cca7236f150b099918_b.jpg" data-caption="" data-size="normal" data-rawwidth="552" data-rawheight="168" class="origin_image zh-lightbox-thumb" width="552" data-original="https://pic1.zhimg.com/v2-92a07e5e1560c1cca7236f150b099918_r.jpg">
<img src="https://pic3.zhimg.com/v2-19c15ba72548ffb535861e5a5625b65a_b.jpg" data-caption="" data-size="normal" data-rawwidth="281" data-rawheight="25" class="content_image" width="281">
7 、總結
1 、浮點型在內存里面的存儲方式
2 、浮點型的精度問題
3 、浮點型的
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。