前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >小数在内存中是如何存储的?

小数在内存中是如何存储的?

作者头像
一头小山猪
发布2020-04-08 15:56:34
3.4K0
发布2020-04-08 15:56:34
举报
文章被收录于专栏:微光点亮星辰微光点亮星辰

写在前面:博主是一只经过实战开发历练后投身培训事业的“小山猪”,昵称取自动画片《狮子王》中的“彭彭”,总是以乐观、积极的心态对待周边的事物。本人的技术路线从Java全栈工程师一路奔向大数据开发、数据挖掘领域,如今终有小成,愿将昔日所获与大家交流一二,希望对学习路上的你有所助益。同时,博主也想通过此次尝试打造一个完善的技术图书馆,任何与文章技术点有关的异常、错误、注意事项均会在末尾列出,欢迎大家通过各种方式提供素材。

  • 对于文章中出现的任何错误请大家批评指出,一定及时修改。
  • 有任何想要讨论和学习的问题可联系我:zhuyc@vip.163.com。
  • 发布文章的风格因专栏而异,均自成体系,不足之处请大家指正。

小数在内存中是如何存储的?

本文关键字:小数、float、double、浮点数、精度

一、IEEE 754(二进制浮点数算术标准)

在学习进制转换时,我们了解到:我们经常使用的十进制数是转换为二进制进行存储的,只需要按照顺序将转换后的结果放在对应的位置上就行了。其实小数的存储也是基于二进制的,不过由于小数由整数部分和小数部分组成,为了方便表示和比较,会使用另外的方式来存储。IEEE 754是最广泛使用的浮点数运算标准,在标准中规定了四种表示浮点数值的方式:

  • 单精度:32位 - 4字节
  • 双精度:64位 - 8字节
  • 延伸单精度:43+
  • 延伸双精度:79+

1. 存储结构

小数在内存中的存储由三部分组成,分别是符号、阶码(或称指数)、尾数。符号位我们很熟悉,只占一位,并且出现在最高位,0为正,1为负。

  • 单精度:符号1位,阶码8位,尾数23位
  • 双精度:符号1位,阶码11位,尾数52位
  • 延伸精度很少使用,不做介绍

2. 存储方式

一个十进制的小数在进行存储时,首先要将整数部分与小数部分都转换为二进制,然后再整理成类似科学计数法的形式,即:移动小数点,使得小数点的左边只有一位,并且只可能为1(因为是二进制),小数点右侧的部分即为尾数部分,移动小数点的位数将会被记录在指数部分中。为了能够透彻的理解十进制小数转化存储在内容中的过程,我们还需要了解一个概念:阶码。

二、阶码(指数)

1. 定义

对于一个二进制数,我们总可以把它整理成:尾数 ✖️ 2的P次方的形式,其中P就被定义为阶码,我们也可以认为2是底数,P为指数,以整数形式表示。

2. 为什么小数被称作浮点数?

  • 定点小数

在早期计算机中,为了节省硬件资源,阶码P的值是被固定的,那么小数的表示形式也同时被固定了。规定第一位为符号位,小数点固定在第一位后面,这种小数是纯小数,被称为定点小数。

  • 浮点小数

与定点小数相对的,如果阶码P可变,那这种小数表示法就被称为浮点表示,这样的数也就被称为浮点数了。更为重要的一点,P指明了小数点的位置。

3. 移码

明白了阶码的概念,也了解了浮点数的前世今生,那么我们大费周章的说这个概念干什么呢?没错,重点来了,就是为了这个移码的码制。在进行小数点移动时,需要先将十进制数转换为二进制,再去移动小数点,保证小数点左侧只有一位,且数值为1。

  • 对于绝对值大于2的数,这个时候我们向左移动小数点,对应的指数为正数;
  • 对于一个绝对值小于1的数,这个时候我们向右移动小数点,对应的指数为负数;
  • 绝对值在1和2之间的数嘞?这个时候不用移动好叭。。。

那么问题就来了,我们的指数有的时候正,有的时候负。But!更为严重的问题是,在指数部分对应的区间并没有符号位这个东西,最前面的符号位代表的是小数本身的正负,这就使得存储和比较都变得困难,所以我们希望通过一种修正的方式避开正负号的问题。怎么做呢?以float为例,指数部分长度为8。原有带符号位的8个bit的存储范围是-128 ~ 127,也就是说可以记录-128次方到+127方之间的所有指数值。如果忽略符号位,把它也当做一个数据的存储位,那么范围就是0~255,我们取这个数的一半作为修正值,即:127,把每次移动小数点后获得的指数值都加上127。

  • 小数点向左移动3位,对应的指数为+3,存入指数部分的值即为130的二进制表示
  • 小数点向右移动2位,对应的指数为-2,存入指数部分的值即为125的二进制表示

这样的好处就是避开了符号的问题,同时,原有的指数的值也得到的了保存,取出的时候减掉127就好了。那么直观的讲,原来的范围是-128 ~ 127,加上127之后范围应该变成-1 ~ 254,貌似对应关系有问题呀~这其实是一个很简单的二进制换算问题,对于有符号数,最高位为符号位,用1代表负数,-128的补码为:1000 0000,但是这在无符号数眼里的值为128,-1的补码为:1111 1111,但是在无符号数眼里值为255。所以我们不能直接通过加减法得出这个取值范围,而应该结合二进制存储的规则。

三、小数的进制转换

说了这么久,我们用几个例子来给大家演示一下,会给大家列出小数在内存中存储的完整表示,在这之前还是需要先学习一下十进制小数应该怎么转换为二进制(读者内心:我太难了。。。)。

1. 十进制转二进制

小数分为整数部分和小数部分,整数部分的转换照常进行,不断的除2得到,小数部分刚好是不断的乘2得到,一直到小数部分为0,或者已经达到了对应的精度,以69.3125为例。

  • 整数部分:69 = 64 + 4 + 1 = 2^6 + 2^2 + 2^0
    • 对应的二进制数为:0100 0101
  • 小数部分:转换过程如下 -> 不断乘2,取出结果中的整数部分
    • 对应的二进制数为:0101
  • 最终转换结果:0100 0101.0101

2. 二进制转十进制

由二进制转换为十进制比较简单,就是运算规则做相反的运算,整数部分是做除法得到的,那么转换回去的时候就是做乘法,小数部分是做乘法得到的,那么转换回去的时候就做除法,以0100 0101.0101为例。

  • 整数部分:2^6 + 2^2 + 2^0 = 64 + 4 + 1 = 69
  • 小数部分:0 x 2^-1 + 1 x 2^-2 + 0 x 2^-3 + 1 x 2^-4 = 0.3125

可以看到规律其实是统一的,就是从左至右根据二进制数乘以2的n次方,从左至右n的值不断递减,在个位处,n的值为0,进入小数部分n的值为负数,在运算上的体现为除法。

3. 小数在内存中的存储表示

  • 99.9

99.9的二进制表示:1100011.111001100110011001100110011001100110011001101。现在我们需要将小数点左移6位,对应的指数值为+6。此时小数点右侧的位数为51位,这些将会被存放在尾数部分,如果使用double类型可以将数据全部记录,但是如果使用float类型,由于尾数部分只有23位,所有只能记录部分的数据,误差也就产生了!整理一下,符号位为0,指数部分为6+127=133,尾数部分直接丢进去,能装多少装多少,以float为例。最终表示为:0 10000101 10001111100110011001100

  • 0.226

0.226的二进制表示:0.0011100111011011001000101101000011100101011000000100001。此时小数点需要右移3位,对应的指数值为-3,剩下的尾数部分同样能塞多少塞多少。整理一下,符号位为0,指数部分为-3+127=124,以float为例。最终表示为:0 1111100 11001110110110010001011

四、float与double

1. 精度范围

从上面的例子我们可以看到,当一个小数在存储的过程中,误差就已经产生了,而且由于是转换为二进制存储,我们很难对所有的小数进行判断是否在存储时丢失了精度。看下面几个例子:

代码语言:javascript
复制
    public static void main(String[] args) {
        Float f1 = 99.99999f;
        System.out.println(f1);// 输出结果:99.99999
        // 貌似很正常啊,其实float的内心慌的一批
        Float f2 = 99.999999f;
        System.out.println(f2);// 输出结果:100.0
        // 此时终于暴露了吧?在存储时就已经丢失了精度,在参与小数计算时更加暴露无遗
    }
  • float精度:小数点后6~7位
  • double精度:小数点后15~16位

丢失精度的原因经过上面的分析和例子相信大家应该很清楚了,我们按照常规流程进行二进制转换后得到的尾数部分可能很长,但是以单精度或双精度进行存储时只能存储一部分,那么必然导致精度的丢失。

2. 解决精度不足

float和double作为基本数据类型使用起来当然是比较方便,但是精度的问题会造成不准确,虽然我们可以通过使用保留几位小数的方式勉强应对,但是为了保证高精度通常会使用BigDecimal,具体用法不在此赘述,将在后续文章中说明。

3. 与长整型的比较

我们在接触基本数据类型的时候曾经碰到过一个大哥大,曾以为能够装进去很大很大的整数,毕竟是8字节的身材,但是仔细那么一比较,存储范围竟然还比不过4字节的float,更不要说同等身材的double了。

  • long的存储范围:-2^63 ~ 2^63 - 1
  • float:-2^128 ~ 2^128
  • double:-2^1024 ~ 2^1024

以上数据只是表示一个量级,不能代表浮点数的精确范围,不过这也足够碾压long类型了,以至于long类型可以隐式转换为float,这就解决了我们的一个疑问,为什么4字节的float存储范围比8字节的long类型还要大?自然是存储方式不同。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-03-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微光点亮星辰 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 小数在内存中是如何存储的?
    • 一、IEEE 754(二进制浮点数算术标准)
      • 1. 存储结构
      • 2. 存储方式
    • 二、阶码(指数)
      • 1. 定义
      • 2. 为什么小数被称作浮点数?
      • 3. 移码
    • 三、小数的进制转换
      • 1. 十进制转二进制
      • 2. 二进制转十进制
      • 3. 小数在内存中的存储表示
    • 四、float与double
      • 1. 精度范围
      • 2. 解决精度不足
      • 3. 与长整型的比较
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档