float,double类型的存储方式和精度丢失
计算机中浮点数的表示、存储方式
- 根据IEEE 754浮点数计数标准,浮点数可以表示
1.
M
.
.
.
∗
2
E
1.M...*2^E
1.M...∗2E采用尾数M+阶码E的编码方式,
因此,只要给出符号(S)、阶码(E)、尾数(M),这三个信息就能完全表示一个浮点数,
- 单精度浮点数float(32位,4字节):
双精度浮点数double(64位,8字节):
- 以单精度浮点型(float)为例:
- Sign(1bit): 符号位。表示浮点数是正数还是负数。0表示正数,1表示负数
- Exponent(8bits):指数部分。对于float来说,这里的8位二进制可以表示256种状态,不过为了表示方便,浮点型的指数位都有一个固定的偏移量(bias),用于使指数+这个偏移量 = 一个非负整数,这样就不用担心如何表示负数了,规定:在32位单精度类型中,这个偏移量是
127
=
2
7
−
1
127=2^7-1
127=27−1。在64位双精度类型中,偏移量是
1023
=
2
10
−
1
1023=2^{10}-1
1023=210−1.
所以, IEEE754规定,指数位用于表示【-126,127】范围内的指数(理论上的范围【-127,128】,但阶数不包括全零和全一的情况,因此它的取值范围是【-126,127】,64位双精度类型中阶数的取值范围是【-1022,+1023】)
关于这里阶数为什么不包含全零和全一的情况,主要是为了保留用作特殊值的处理,NaN、 ±0,±∞、以及非规范化数。
对于单精度浮点数,所有这些特殊值都由保留的特殊指数值 -127 和 128 来编码。如果我们分别用 emin 和 emax 来表达其它常规指数值范围的边界,即 -126 和 127,则保留的特殊指数值可以分别表达为 emin - 1 和 emax + 1;
详细参考https://blog.csdn.net/hyforthy/article/details/19649969
对于单精度浮点数,举例:
如果运算后得到的指数是-126,则加上偏移量127后,这里的8bit空间填写1;
如果运算后得到的指数是-10,则加上偏移量127后,这里的8bit空间填写117;
看得出来,有了偏移量,指数位中始终是一个非负整数。
- fraction(23bits):基数部分。浮点数具体数值的实际表示。
举例:对于一个十进制数20.75,其在float类型变量中的存储
第一步:将十进制化为二进制(整数部分:除k取余法,结果逆序排列;小数部分:乘k取整法,结果正序排列)
可知:十进制数20.75,表示成二进制数是10100.11。
第二步,将其转换成阶码+尾数表示
1.
M
.
.
.
∗
2
E
1.M...*2^E
1.M...∗2E
10100.11
=
1.010011
∗
2
4
10100.11=1.010011*2^4
10100.11=1.010011∗24。
第三步,确定符号位,指数部分,尾数部分的数值,
Sign(1bit): 0
指数部分(8bits):127+4=131=1000 0011
尾数部分(23bits):010011=0100 1100 0000 0000 0000 000(黄色部分为补零位)
所以float存储格式位
0 1000 0011 0100 1100 0000 0000 0000 000
可以在线验证:
http://www.binaryconvert.com/result_float.html?decimal=050048046055053
float、double的范围
先看个程序
#include <iostream>
#include <cfloat>
#include<limits>
using namespace std;
int main ()
{
cout<<"numeric_limits<int>::max()="<<numeric_limits<int>::max()<<endl;
cout<<"numeric_limits<int>::min()="<<numeric_limits<int>::min()<<endl;
cout<<"INT_MAX= "<<INT_MAX<<endl;
cout<<"INT_MIN= "<<INT_MIN<<endl;
cout<<"numeric_limits<float>::max()="<<numeric_limits<float>::max()<<endl;
cout<<"numeric_limits<float>::min()="<<numeric_limits<float>::min()<<endl;
cout<<"FLT_MAX= "<<FLT_MAX<<endl;
cout<<"FLT_MIN= "<<FLT_MIN<<endl;
cout<<"numeric_limits<double>::max()="<<numeric_limits<double>::max()<<endl;
cout<<"numeric_limits<double>::min()="<<numeric_limits<double>::min()<<endl;
cout<<"DBL_MAX= "<<DBL_MAX<<endl;
cout<<"DBL_MIN= "<<DBL_MIN<<endl;
system("pause");
return 0;
}
对于INT_MAX和INT_MIN经常使用,很好理解,就是有符号int(32位数)的范围.
#define INT_MAX 2147483647
#define INT_MIN (-INT_MAX - 1)
在C/C++语言中,不能够直接使用-2147483648来代替最小负数,因为这不是一个数字,而是一个表达式。表达式的意思是对整数21473648取负,但是2147483648已经溢出了int的上限,所以定义为(-INT_MAX -1)
但float,double也是有符号的呀,这里怎么是 F L T _ M A X = 3.40282 e + 38 ; FLT\_MAX=3.40282e^{+38}; FLT_MAX=3.40282e+38; F L T _ M I N = 1.17549 e − 38 FLT\_MIN=1.17549e^{-38} FLT_MIN=1.17549e−38 D B L _ M A X = 1.79769 e + 308 DBL\_MAX=1.79769e^{+308} DBL_MAX=1.79769e+308 D B L _ M I N = 2.22507 e − 308 DBL\_MIN=2.22507e^{-308} DBL_MIN=2.22507e−308
这里先给出浮点类型的范围
float | 左边界 | 右边界 |
---|---|---|
负数部分 | − 3.4028 ∗ 1 0 − 38 -3.4028*10^{-38} −3.4028∗10−38 | − 1.17755 ∗ 1 0 − 38 -1.17755*10^{-38} −1.17755∗10−38 |
正数部分 | 1.17755 ∗ 1 0 − 38 1.17755*10^{-38} 1.17755∗10−38 | 3.4028 ∗ 1 0 − 38 3.4028*10^{-38} 3.4028∗10−38 |
double | 左边界 | 右边界 |
---|---|---|
负数部分 | − 1.7977 ∗ 1 0 − 308 -1.7977*10^{-308} −1.7977∗10−308 | − 2.2251 ∗ 1 0 − 308 -2.2251*10^{-308} −2.2251∗10−308 |
正数部分 | 2.2251 ∗ 1 0 − 308 2.2251*10^{-308} 2.2251∗10−308 | 1.7977 ∗ 1 0 − 308 1.7977*10^{-308} 1.7977∗10−308 |
注意:在<float.h>中定义了浮点类型的范围,
参考;https://docs.microsoft.com/en-us/cpp/c-language/limits-on-floating-point-constants?view=vs-2019
#define FLT_MAX 3.402823466e+38F
#define FLT_MIN 1.175494351e-38F
#define DBL_MAX 1.7976931348623158e+308
#define DBL_MIN 2.2250738585072014e-308
由此可知
FLT_MAX,FLT_MIN,分别表示的是float类型正数部分上界和下界
DBL_MAX,DBL_MIN,分别表示的是double类型正数部分是上界和下界
- 关于怎么计算浮点型float,double的取值范围
参考:https://stackoverflow.com/questions/4610999/how-to-calculate-double-float-precision
结论:精确计算浮点类型表示的最大值的方法为 2 ( 2 ( e − 1 ) ) ( 1 − 2 ( − p ) ) 2^{(2^{(e-1)} )}(1-2^{(-p)}) 2(2(e−1))(1−2(−p))
其中e是指数部分E位数,p是尾数部分M位数+1
关于这里的公式怎么来的,推导如下:首先 1. M . . . ∗ 2 E 1.M...*2^E 1.M...∗2E
- 由前面可知,E的范围的最大值 e m a x e_{max} emax是 2 e − 1 − 1 2^{e-1}-1 2e−1−1
float的指数部分位数e=8,取值范围是【-126,127】, 最大值127
double的指数部分位数e=11,取值范围是【-1022,+1023】,,最大值1023
2.最大值为
1.1111...
(
小
数
点
后
有
M
个
1
)
∗
2
e
m
a
x
1.1111...(小数点后有M个1)* 2^{e_{max}}
1.1111...(小数点后有M个1)∗2emax,其中
e
m
a
x
e_{max}
emax是
2
e
−
1
−
1
2^{e-1}-1
2e−1−1
二
进
制
数
1.1111...
(
小
数
点
后
有
M
个
1
)
=
2
0
+
2
−
1
+
2
−
2
+
2
−
3
+
.
.
.
+
2
−
M
二进制数1.1111...(小数点后有M个1)=2^0+2^{-1}+2^{-2}+2^{-3}+...+2^{-M}
二进制数1.1111...(小数点后有M个1)=20+2−1+2−2+2−3+...+2−M
= 2 ( 1 − 2 − ( M + 1 ) ) =2(1-2^{-(M+1)}) =2(1−2−(M+1)),将M+1记为p,
则最大值为 = 2 ( 1 − 2 − p ) ∗ 2 ( 2 e − 1 − 1 ) = 2 ( 2 ( e − 1 ) ) ( 1 − 2 ( − p ) ) =2(1-2^{-p})*2^{(2^{e-1}-1)}=2^{(2^{(e-1)} )}(1-2^{(-p)}) =2(1−2−p)∗2(2e−1−1)=2(2(e−1))(1−2(−p)),即为所求
所以,
-
对于 f l o a t , e = 8 , p = 24 , float,e=8,p=24, float,e=8,p=24,精确的最大值为 2 128 ∗ ( 1 − 2 − 24 ) 2^{128}*(1 - 2^{-24)} 2128∗(1−2−24)=
340282346638528859811704183484516925440 340282346638528859811704183484516925440 340282346638528859811704183484516925440 约等于 3.4 × 1 0 38 3.4 × 10^{38} 3.4×1038 -
对于 d o u b l e , e = 10 , p = 53 , double,e = 10 , p = 53, double,e=10,p=53,精确的最大值为 2 1024 ∗ ( 1 − 2 − 53 ) 2^{1024} * (1 - 2^{-53}) 21024∗(1−2−53)=
179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368 约等于 1.80 × 1 0 308 1.80 × 10^{308} 1.80×10308
类型 | 符号 | 尾数 | 指数 |
---|---|---|---|
float | 数符1位(±) | 尾数部分 23位(决定精度) | -126~127 指数8位(决定范围) |
double | 数符1位(±) | 尾数部分 52位(决定精度) | -1022~1023 指数11位(决定范围) |
- 经过上面的分析,容易得出float和double的精确范围如下:
float | 左边界 | 右边界 |
---|---|---|
负数部分 | − 2 128 ∗ ( 1 − 2 − 24 ) -2^{128}*(1 - 2^{-24)} −2128∗(1−2−24) | − 1.0 ∗ 2 − 126 -1.0*2^{-126} −1.0∗2−126 |
正数部分 | 1.0 ∗ 2 − 126 1.0*2^{-126} 1.0∗2−126 | 2 128 ∗ ( 1 − 2 − 24 ) 2^{128}*(1 - 2^{-24)} 2128∗(1−2−24) |
double | 左边界 | 右边界 |
---|---|---|
负数部分 | − 2 1024 ∗ ( 1 − 2 − 53 ) -2^{1024}*(1 - 2^{-53)} −21024∗(1−2−53) | − 1.0 ∗ 2 − 1022 -1.0*2^{-1022} −1.0∗2−1022 |
正数部分 | 1.0 ∗ 2 − 1022 1.0*2^{-1022} 1.0∗2−1022 | 2 1024 ∗ ( 1 − 2 − 53 ) 2^{1024}*(1 - 2^{-53)} 21024∗(1−2−53) |
关于0在浮点数中的存储,请看下面面的特殊值部分
- 将上面的精确数据取约等于,则可得如下的范围
float | 左边界 | 右边界 |
---|---|---|
负数部分 | − 3.4028 ∗ 1 0 − 38 -3.4028*10^{-38} −3.4028∗10−38 | − 1.17755 ∗ 1 0 − 38 -1.17755*10^{-38} −1.17755∗10−38 |
正数部分 | 1.17755 ∗ 1 0 − 38 1.17755*10^{-38} 1.17755∗10−38 | 3.4028 ∗ 1 0 − 38 3.4028*10^{-38} 3.4028∗10−38 |
double | 左边界 | 右边界 |
---|---|---|
负数部分 | − 1.7977 ∗ 1 0 − 308 -1.7977*10^{-308} −1.7977∗10−308 | − 2.2251 ∗ 1 0 − 308 -2.2251*10^{-308} −2.2251∗10−308 |
正数部分 | 2.2251 ∗ 1 0 − 308 2.2251*10^{-308} 2.2251∗10−308 | 1.7977 ∗ 1 0 − 308 1.7977*10^{-308} 1.7977∗10−308 |
- 浮点数虽然表示范围大,但是其表示的浮点数在其范围内并不是连续的。
每两个数所相隔的距离不是平均相等的,往往数越大间隔也越来越大。
浮点数的精度丢失问题
参考:
https://blog.csdn.net/qq_36470686/article/details/83794997
https://blog.csdn.net/zhrh0096/article/details/38589067
-
为何浮点数可能丢失精度?
因为浮点十进制值通常没有完全相同的二进制表示形式 -
现在我们就详细剖析一下浮点型运算为什么会造成精度丢失?
- 小数的二进制表示问题
首先我们要搞清楚下面两个问题:
(1) 十进制整数如何转化为二进制数
算法很简单。举个例子,11表示成二进制数:
11/2=5 余 1
5/2=2 余 1
2/2=1 余 0
1/2=0 余 1
0结束 11二进制表示为(从下往上):1011
这里提一点:只要遇到除以后的结果为0了就结束了,大家想一想,所有的整数除以2是不是一定能够最终得到0。换句话说,所有的整数转变为二进制数的算法会不会无限循环下去呢?绝对不会,整数永远可以用二进制精确表示,但小数就不一定了。
(2) 十进制小数如何转化为二进制数
算法是乘以2直到没有了小数为止。举个例子,0.9表示成二进制数
0.9
∗
2
=
1.8
0.9*2=1.8
0.9∗2=1.8 取整数部分 1
0.8
0.8
0.8(
1.8
1.8
1.8的小数部分)
∗
2
=
1.6
*2=1.6
∗2=1.6 取整数部分 1
0.6
∗
2
=
1.2
0.6*2=1.2
0.6∗2=1.2取整数部分 1
0.2
∗
2
=
0.4
0.2*2=0.4
0.2∗2=0.4取整数部分 0
0.4
∗
2
=
0.8
0.4*2=0.8
0.4∗2=0.8 取整数部分 0
0.8
∗
2
=
1.6
0.8*2=1.6
0.8∗2=1.6 取整数部分 1
0.6
∗
2
=
1.2
0.6*2=1.2
0.6∗2=1.2取整数部分 0
.
.
.
.
.
.
.
.
.
0.9
......... 0.9
.........0.9二进制表示为(从上往下):
1100100100100......
1100100100100......
1100100100100......
注意:上面的计算过程循环了,也就是说*2永远不可能消灭小数部分,这样算法将无限下去。很显然,小数的二进制表示有时是不可能精确的。其实道理很简单,十进制系统中能不能准确表示出1/3呢?同样二进制系统也无法准确表示1/10。这也就解释了为什么浮点型减法出现了"减不尽"的精度丢失问题。
- 因为精度的问题,我们往往不用
f
l
o
a
t
float
float a==
f
l
o
a
t
float
float b或
f
l
o
a
t
float
float a==
0
0
0进行判断!
解决办法:
(1) float和double只能用来做科学计算或者是工程计算,在商业计算中我们可以使用java中的java.math.BigDecimal。使用BigDecimal并且一定要用String来够造。
(2) 先化为整数再比较。比如23.56与23.49比较,我们化为整数,比较2356与2349大小。
浮点数的有效位数
首先应该明确:有效数字是指 整数部分 和小数部分一共的有效位数
结论:
- float有效位数为7位
- double的有效位数为15位
单精度数的尾数用23位存储,加上默认的小数点前的1位1, 2 ( 23 + 1 ) − 1 = 16777215 2^{(23+1)} -1= 16777215 2(23+1)−1=16777215,可以看出对于7位有效数字,完全不会超过16777215(8位),所以说单精度浮点数的有效位数是7位。
双精度的尾数用52位存储,
2
(
52
+
1
)
−
1
=
9007199254740991
2^{(52+1)} -1= 9007199254740991
2(52+1)−1=9007199254740991,
可以看出对于15位有效数字,完全不会超过9007199254740991(16位),所以双精度的有效位数是15位。