float,double类型的存储方式和精度丢失

计算机中浮点数的表示、存储方式

  1. 根据IEEE 754浮点数计数标准,浮点数可以表示 1. M . . . ∗ 2 E 1.M...*2^E 1.M...2E采用尾数M+阶码E的编码方式,
    因此,只要给出符号(S)、阶码(E)、尾数(M),这三个信息就能完全表示一个浮点数,
  • 单精度浮点数float(32位,4字节):
    在这里插入图片描述
    双精度浮点数double(64位,8字节):
    在这里插入图片描述
  1. 以单精度浮点型(float)为例:
  • Sign(1bit): 符号位。表示浮点数是正数还是负数。0表示正数,1表示负数
  • Exponent(8bits):指数部分。对于float来说,这里的8位二进制可以表示256种状态,不过为了表示方便,浮点型的指数位都有一个固定的偏移量(bias),用于使指数+这个偏移量 = 一个非负整数,这样就不用担心如何表示负数了,规定:在32位单精度类型中,这个偏移量是 127 = 2 7 − 1 127=2^7-1 127=271。在64位双精度类型中,偏移量是 1023 = 2 10 − 1 1023=2^{10}-1 1023=2101.
    所以, 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.01001124
第三步,确定符号位,指数部分,尾数部分的数值,
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.17549e38 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.22507e308

这里先给出浮点类型的范围

float左边界右边界
负数部分 − 3.4028 ∗ 1 0 − 38 -3.4028*10^{-38} 3.40281038 − 1.17755 ∗ 1 0 − 38 -1.17755*10^{-38} 1.177551038
正数部分 1.17755 ∗ 1 0 − 38 1.17755*10^{-38} 1.177551038 3.4028 ∗ 1 0 − 38 3.4028*10^{-38} 3.40281038
double左边界右边界
负数部分 − 1.7977 ∗ 1 0 − 308 -1.7977*10^{-308} 1.797710308 − 2.2251 ∗ 1 0 − 308 -2.2251*10^{-308} 2.225110308
正数部分 2.2251 ∗ 1 0 − 308 2.2251*10^{-308} 2.225110308 1.7977 ∗ 1 0 − 308 1.7977*10^{-308} 1.797710308

注意:在<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类型正数部分是上界和下界

关于这里的公式怎么来的,推导如下:首先 1. M . . . ∗ 2 E 1.M...*2^E 1.M...2E

  1. 由前面可知,E的范围的最大值 e m a x e_{max} emax 2 e − 1 − 1 2^{e-1}-1 2e11

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...(M1)2emax,其中 e m a x e_{max} emax 2 e − 1 − 1 2^{e-1}-1 2e11
二 进 制 数 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...(M1)=20+21+22+23+...+2M

= 2 ( 1 − 2 − ( M + 1 ) ) =2(1-2^{-(M+1)}) =2(12(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(12p)2(2e11)=2(2(e1))(12(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(1224)=
    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, doublee=10p=53精确的最大值为 2 1024 ∗ ( 1 − 2 − 53 ) 2^{1024} * (1 - 2^{-53}) 21024(1253)=
    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(1224) − 1.0 ∗ 2 − 126 -1.0*2^{-126} 1.02126
正数部分 1.0 ∗ 2 − 126 1.0*2^{-126} 1.02126 2 128 ∗ ( 1 − 2 − 24 ) 2^{128}*(1 - 2^{-24)} 2128(1224)
double左边界右边界
负数部分 − 2 1024 ∗ ( 1 − 2 − 53 ) -2^{1024}*(1 - 2^{-53)} 21024(1253) − 1.0 ∗ 2 − 1022 -1.0*2^{-1022} 1.021022
正数部分 1.0 ∗ 2 − 1022 1.0*2^{-1022} 1.021022 2 1024 ∗ ( 1 − 2 − 53 ) 2^{1024}*(1 - 2^{-53)} 21024(1253)

关于0在浮点数中的存储,请看下面面的特殊值部分

在这里插入图片描述

  • 将上面的精确数据取约等于,则可得如下的范围
float左边界右边界
负数部分 − 3.4028 ∗ 1 0 − 38 -3.4028*10^{-38} 3.40281038 − 1.17755 ∗ 1 0 − 38 -1.17755*10^{-38} 1.177551038
正数部分 1.17755 ∗ 1 0 − 38 1.17755*10^{-38} 1.177551038 3.4028 ∗ 1 0 − 38 3.4028*10^{-38} 3.40281038
double左边界右边界
负数部分 − 1.7977 ∗ 1 0 − 308 -1.7977*10^{-308} 1.797710308 − 2.2251 ∗ 1 0 − 308 -2.2251*10^{-308} 2.225110308
正数部分 2.2251 ∗ 1 0 − 308 2.2251*10^{-308} 2.225110308 1.7977 ∗ 1 0 − 308 1.7977*10^{-308} 1.797710308
  • 浮点数虽然表示范围大,但是其表示的浮点数在其范围内并不是连续的。
    每两个数所相隔的距离不是平均相等的,往往数越大间隔也越来越大。

浮点数的精度丢失问题

参考:
https://blog.csdn.net/qq_36470686/article/details/83794997

https://blog.csdn.net/zhrh0096/article/details/38589067

  • 为何浮点数可能丢失精度?
    因为浮点十进制值通常没有完全相同的二进制表示形式

  • 现在我们就详细剖析一下浮点型运算为什么会造成精度丢失?

  1. 小数的二进制表示问题
    首先我们要搞清楚下面两个问题:

(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.92=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.62=1.2取整数部分 1
0.2 ∗ 2 = 0.4 0.2*2=0.4 0.22=0.4取整数部分 0
0.4 ∗ 2 = 0.8 0.4*2=0.8 0.42=0.8 取整数部分 0
0.8 ∗ 2 = 1.6 0.8*2=1.6 0.82=1.6 取整数部分 1
0.6 ∗ 2 = 1.2 0.6*2=1.2 0.62=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位。

详解:
https://blog.csdn.net/bluewanderer/article/details/86671653?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值