二更,因为好几个人因为这篇文章把我批斗了,把有问题的地方修正。
今天看到一个问题
能不能用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可以表示为
570795 * 2^{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()
{
float num = 3.14159f;
int *p = (int *)#
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~~~~
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
4e+38
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 ;
}
输出
00000000000000000000000000000000
-2.12345671653747558593750000000000
99999904632568359375000000000000
0000000000000000000000000000000000000000000000000000000000000000
-4.0000000000000000000000000000000000000000000000000000000000000000
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()
{
//int it = 0b01000000010010010000111111010000;
int it = 0b00111111100000000000000000000000;
float *num = (float*)⁢
float num1;
int *p = (int *)&num1;
printf("%f\r\n",*num);
printf("%f\r\n",num1);
printf("0x%x\r\n",*p);
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、浮点型的