当前位置: 首页 > news >正文

float的精度和取值范围

文章目录

  • 前言
  • 精度限制
  • float存储方式
  • float存储示例
  • float范围
  • float精度
  • float小数
  • float特殊值
  • 总结

前言

关于float的精度和取值范围这个问题,我查询了很多次,每次都是用完就忘了,等到再使用的时候还需要再次查询,关键是这个问题大家给出的结果并不都是一致的,我得从众多的资料当中选择出正确的观点,这还要额外花一些时间,所以我决定也总结一次,方便我以后拿来直接用了,如果能给大家带来帮助那就更好了。下面提到一些说法很多都是我个人的理解,如果大家有疑义,欢迎讨论。

精度限制

首先考虑下为什么会产生精度问题,是因为存储数据的空间有限,以一个四字节整数int n;为例,一共有32位,取值范围是 [-2147483648‬, 2147483647] ,一共是4,294,967,296种可能,它的精度可以说是小数点后一位都不保留,也就是只有整数,换句话说变量n可以表示实数范围内的4,294,967,296个数值。

如果换成float类型呢?一个变量float f所能表示多少个数呢?实际上由于存储空间未发生变化,同样是4字节32位,那么float类型也只能表示,或者说精确表示4,294,967,296个数值(真实情况由于一些特殊的规则,最终所表示的数字个数还要少),说到这里很多人可能会疑惑,因为他知道float可以表示比4,294,967,296大的数,同时也能表示小数,如果只有4,294,967,296种可能,那究竟是怎么做到的呢?

这里也就开始提到精度了,整数很好理解,每个数字的间隔都是1,int类型所表示的4,294,967,296个数字都是等间距的,步长为1。而float也只能表示4,294,967,296个数字,同时要表示比int还大的范围,一个很直观的想法就是把间距拉大,这样范围就大了,但是float还要表示小数,像0.2、0.4这样的数字间距明显要小于1啊,想要存储小数貌似要把间距缩小,这就和前面矛盾了啊。

实际上float类型存储数据的间隔不是等间距的,而是在0的附近间距小,在远离0的位置间距大,为什么会这样,一会我们看一下float类型数据的存储规则就明白了,这里先来看一下int类型和float类型所表示数字的范围对比,这只是一个示意图。

//int
           [ *         *         *         0         *         *         * ]
//float
[ *          *    *    *   *  *  * * * * * 0 * * * * *  *  *   *    *    *          * ]

上面的示意图就是两者表示数字范围的差异,每个星号*就表示一个数字,float通过这种不等间距的分布,既扩大了范围也表示了小数,那么有没有问题呢?

当然有问题,饭就这么多,人多了自然不够吃了,因为远离0的位置间距越来越大,当要表示间距中间的一个数字时,只能找它附近离它最近的一个可以表示的数字来代替,这就导致了精度问题,比如我给一个float类型变量分别赋值为 4294967244 和 4294967295 ,再次输出时都变成了 4294967296,因为超过了精度,所以只能找最接近的数字代替。

float存储方式

这部分内容基本上各篇文章说的都一致,我也简单描述下,后面根据这部分的定义来推算一下float的精度和取值范围。

首先我们知道常用科学计数法是将所有的数字转换成(±)a.b x 1 0 c 10^c 10c 的形式,其中a的范围是1到9共9个整数,b是小数点后的所有数字,c是10的指数。而计算机中存储的都是二进制数据,所以float存储的数字都要先转化成(±)a.b x 2 c 2^c 2c,由于二进制中最大的数字就是1,所以表示法可以写成(±)1.b x 2 c 2^c 2c的形式,float要想存储小数就只需要存储(±),b和c就可以了。

float的存储正是将4字节32位划分为了3部分来分别存储正负号,小数部分和指数部分的:

  1. Sign(1位):用来表示浮点数是正数还是负数,0表示正数,1表示负数。
  2. Exponent(8位):指数部分。即上文提到数字c,但是这里不是直接存储c,为了同时表示正负指数以及他们的大小顺序,这里实际存储的是c+127。
  3. Mantissa(23位):尾数部分。也就是上文中提到的数字b。

三部分在内存中的分布如下,用首字母代替类型

SEEEEEEEEMMMMMMMMMMMMMMMMMMMMMM
0100000011000110011001100110011

float存储示例

以数字6.5为例,看一下这个数字是怎么存储在float变量中的:

  1. 先来看整数部分,模2求余可以得到二进制表示为110。

  2. 再来看小数部分,乘2取整可以得到二进制表示为.1(如果你不知道怎样求小数的二进制,请主动搜索一下)。

  3. 拼接在一起得到110.1然后写成类似于科学计数法的样子,得到1.101 x 2 2 2^2 22

  4. 从上面的公式中可以知道符号为正,尾数是101,指数是2。

  5. 符号为正,那么第一位填0,指数是2,加上偏移量127等于129,二进制表示为10000001,填到2-9位,剩下的尾数101填到尾数位上即可

SEEEEEEEEMMMMMMMMMMMMMMMMMMMMMM
0100000011010000000000000000000
  1. 内存中二进制数01000000 11010000 00000000 00000000表示的就是浮点数6.5

float范围

明白了上面的原理就可求float类型的范围了,找到所能表示的最大值,然后将符号为置为1变成负数就是最小值,要想表示的值最大肯定是尾数最大并且指数最大,
那么可以得到尾数为 0.1111111 11111111 11111111,指数为 11111111,但是指数全为1时有其特殊用途,所以指数最大为 11111110,指数减去127得到127,所以最大的数字就是1.1111111 1111111 11111111 x 2 127 2^{127} 2127,这个值为 340282346638528859811704183484516925440,通常表示成 3.4028235E38,那么float的范围就出来了:

[-3.4028235E38, 3.4028235E38]

float精度

float 类型的数据精度取决于尾数,相信大家都知道这一点,但是精度怎么算我也是迷糊了好久,最近在不断尝试的过程中渐渐的明白了,首先是在不考虑指数的情况下23位尾数能表示的范围是[0, 2 23 − 1 2^{23}-1 2231],实际上尾数位前面还隐含了一个"1",所以应该是一共24位数字,所能表示的范围是[0, 2 24 − 1 2^{24}-1 2241](因为隐含位默认是"1",所以表示的数最小是1不是0,但是先不考虑0,后面会特殊介绍,这里只按一般值计算),看到这里我们知道这24位能表示的最大数字为 2 24 2^{24} 224-1,换算成10进制就是16777215,那么[0, 16777215]都是能精确表示的,因为他们都能写成1.b x 2 c 2^c 2c的形式,只要配合调整指数c就可以了。

16777215 这个数字可以写成1.1111111 11111111 1111111 * 2 23 2^{23} 223,所以这个数可以精确表示,然后考虑更大的数16777216,因为正好是2的整数次幂,可以表示1.0000000 00000000 00000000 * 2 24 2^{24} 224,所以这个数也可以精确表示,在考虑更大的数字16777217,这个数字如果写成上面的表示方法应该是 1.0000000 00000000 00000000 1 * 2 24 2^{24} 224,但是这时你会发现,小数点后尾数位已经是24位了,23位的存储空间已经无法精确存储,这时浮点数的精度问题也就是出现了。

看到这里发现 16777216 貌似是一个边界,超过这个数的数字开始不能精确表示了,那是不是所有大于16777216的数字都不能精确表示了呢?其实不是的,比如数字 33554432 就可以就可以精确表示成1.0000000 00000000 00000000 * 2 25 2^{25} 225,说道这里结合上面提到的float的内存表示方式,我们可以得出大于 16777216 的数字(不超上限),只要可以表示成小于24个2的n次幂相加,并且每个n之间的差值小于24就能够精确表示。换句话来说所有大于 16777216 的合理数字,都是[0, 16777215]范围内的精确数字通过乘以 2 n 2^n 2n得到的,同理所有小于1的正数,也都是 [0, 16777215] 范围内的精确数字通过乘以 2 n 2^n 2n得到的,只不过n取负数就可以了。

16777216 已经被证实是一个边界,小于这个数的整数都可以精确表示,表示成科学技术法就是1.6777216 * 1 0 7 10^{7} 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 2 c 2^c 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.f * 2^{-126} 0.f2126非标准值,尾数前改为0,提高了精度
1全为0任意取值f − 0. f ∗ 2 − 126 -0.f * 2^{-126} 0.f2126非标准值,尾数前改为0,提高了精度
0全为1全为0+Infinity正无穷大
1全为1全为0-Infinity负无穷大
0/1全为1不全为0NaN非数字,用来表示一些特殊情况

总结

  1. float的精度是保证至少7位有效数字是准确的
  2. float的取值范围[-3.4028235E38, 3.4028235E38],精确范围是[-340282346638528859811704183484516925440, 340282346638528859811704183484516925440]
  3. 一个简单的测试float精度方法,C++代码中将数字赋值给float变量,如果给出警告warning C4305: “=”: 从“int”到“float”截断,则超出了float的精度范围,在我的测试中赋值为16777216及以下整数没有警告,赋值为16777217时给出了警告。

相关文章:

  • linux环境下常用的打包、压缩、解压命令(tar、gzip、bzip2、zip)
  • MySQL数据库导入、导出、复制表、重命名表
  • Python操作Excel工作簿(\*.xlsx)
  • java对接银联商务公众号+服务窗支付(1)
  • java对接银联商务公众号+服务窗支付(2)
  • java对接银联商务公众号+服务窗支付(3)
  • java对接银联商务公众号+服务窗支付(4)
  • java对接银联商务公众号+服务窗支付(5)
  • java对接银联商务扫码支付
  • 报错:Invalid bound statement (not found): com.wshy.example.dao.UserDao.getUsers
  • Field userDao ....service.impl...'com.lzj.springcloud.dao.UserDao' that could not be found
  • java对接银联商务扫码支付(银联商务扫码支付文档)
  • Java中拦截器的使用
  • 记项目中出现的数据库若干问题和注意事项
  • 工行icbc 在线支付jar包下载及maven导入本地库
  • [nginx文档翻译系列] 控制nginx
  • 【391天】每日项目总结系列128(2018.03.03)
  • 【vuex入门系列02】mutation接收单个参数和多个参数
  • 【个人向】《HTTP图解》阅后小结
  • C++回声服务器_9-epoll边缘触发模式版本服务器
  • CODING 缺陷管理功能正式开始公测
  • ComponentOne 2017 V2版本正式发布
  • k8s如何管理Pod
  • ng6--错误信息小结(持续更新)
  • PHP 小技巧
  • REST架构的思考
  • TypeScript迭代器
  • 思维导图—你不知道的JavaScript中卷
  • ​sqlite3 --- SQLite 数据库 DB-API 2.0 接口模块​
  • $.ajax()
  • (03)光刻——半导体电路的绘制
  • (翻译)terry crowley: 写给程序员
  • (附源码)基于SpringBoot和Vue的厨到家服务平台的设计与实现 毕业设计 063133
  • (一)Thymeleaf用法——Thymeleaf简介
  • (一)u-boot-nand.bin的下载
  • (原)本想说脏话,奈何已放下
  • .describe() python_Python-Win32com-Excel
  • .NET 中创建支持集合初始化器的类型
  • .net6解除文件上传限制。Multipart body length limit 16384 exceeded
  • .Net6支持的操作系统版本(.net8已来,你还在用.netframework4.5吗)
  • .net图片验证码生成、点击刷新及验证输入是否正确
  • .NET微信公众号开发-2.0创建自定义菜单
  • .Net下使用 Geb.Video.FFMPEG 操作视频文件
  • .NET性能优化(文摘)
  • [2008][note]腔内级联拉曼发射的,二极管泵浦多频调Q laser——
  • [2018/11/18] Java数据结构(2) 简单排序 冒泡排序 选择排序 插入排序
  • [Android]竖直滑动选择器WheelView的实现
  • [Angular] 笔记 20:NgContent
  • [AutoSAR系列] 1.3 AutoSar 架构
  • [CVPR2021]Birds of a Feather: Capturing Avian Shape Models from Images
  • [GYCTF2020]Ez_Express
  • [Linux] day07——查看及过滤文本
  • [Linux](15)线程基础,线程控制,线程的互斥与同步
  • [one_demo_11]二分查找法
  • [POI2007] ZAP-Queries (莫比乌斯反演)