前言:

这篇博文,我写了好几天……,里面涉及的基础概念比较多,内容比较多,举例也比较多,想搞清楚明白就难免 我自己都会觉得啰嗦,我整理了目录出来,看完需要一点时间,可以自行根据需要、感兴趣的 选择阅读、浏览,如果你有耐心也可以全部看完,哈哈哈……


目录

浮点数概念:

float类型(单精度)的存储格式:

float类型(单精度)的33.14:

科学计数法表示float类型(单精度)的二进制小数:

按照IEEE 754标准存储 float (单精度)浮点数:

说明:

double类型(双精度)的存储格式:

double类型(双精度)的33.14:

科学计数法表示double类型(双精度)的二进制小数:

按照IEEE 754标准存储 double (双精度)浮点数:

IEEE 754标准定义的浮点数四种精度类型的分类归纳:

IEEE 754标准定义的 六大类 浮点数(理解):

(单精度,双精度)浮点数中的最值表示归纳:

Java程序输出 float类型和 double类型浮点数的二进制码:

对于双精度浮点数存储时尾数最低位不用 加一 存储的大概规则:

浮点数的舍入(了解)(浮点数偶尔运算时产生精度丢失 原因浅析)

单精度浮点数和双精度浮点数的有效数字(有效位数,有效精度,有效范围) 是怎么算出来的?

单精度的有效位数 即:

双精度的有效位数 即:

延伸双精度的有效位数 即:

单精度的有效位数是:

双精度的有效位数是:

延伸双精度的有效位数是:

总结:


浮点数概念:

浮点数,在数学中称为实数,是指小数点在逻辑上不固定的数,(是属于有理数中某特定子集的数的数字表示,在计算机中用以近似表示任意某个实数。具体的说,这个实数由一个整数或定点数(即尾数)乘以某个基数(计算机中通常是2)的整数次幂得到,这种表示方法类似于基数为10的科学计数法。)

在计算机中与所有的其他数据一样,浮点数也要用二进制表示,但是浮点数的二进制表示形式并没有像整数那么简单。

IEEE 在1985年制定了IEEE 754标准 https://baike.baidu.com/item/IEEE%20754/3869922?fr=aladdin 统一浮点数的存储格式,现在,大多数的计算机都遵守这一标准,在很大的程度上改善了各种软件的可移植性。

在各大编程语言中,最长常见的两类浮点数是单精度(Java中用 float 类型表示)和双精度(Java中用 double 类型表示)浮点数,IEEE 754对它们的存储格式做了严格的规定!


下面由我来浅谈一下浮点数的存储格式吧:

先看一张图:

为什么不同类型的整数比较会相等,而不同类型的浮点数比较却不相等呢? 整数都知道,它的存储相对来说比较简单,

比如: int类型 的 6688 二进制码为: 00000000 00000000 00011010 00100000 (4个字节(也就是32位))

long类型的 6688 二进制码为: 00000000 00000000 00000000 00000000 00000000 00000000 00011010 00100000 (8个字节(也就是64位))

它们在底层存储的格式是一样的,只是6688在int 类型和 long类型中所占用的字节大小不一样而已,这很简单

但为什么浮点数的float和double相比较就不行呢?

下面先以单精度浮点数为例,再把双精度浮点数也总结归纳一下:


float类型(单精度)的存储格式:

IEEE 754的标准规定存储格式:即:

规格化数 = 符号位(sign) X  阶码(exponent)X 尾数(mantissa)

N = (-1)S X 2E-127 X 1.M

单精度浮点数(Java中 float 类型)在存储时是占用的4个字节,即32位 , 符号位和阶码和尾数的格式如图所示:

                       float类型   32位

说明:

  1. sign: 用 1 位来表示符号位,若浮点数是正数,则符号位为 “0”,若是负数,则符号位为 “1”

  2. exponent: 用 8 位来表示存储的阶码,存储的阶码等于规格化数中的指数加上127(固定指数偏移量值),即 阶码 = 指数 + 127(固定指数偏移量值), 8位表示的存储数值容量可以是255个数字(2^8-1、2的八次方-1(减-就是减的那个符号位)),指数取值数据范围包括正负数就是(-126 ~ 127),由于指数可能是负数,为了处理负指数的情况,IEEE 754 要求指数必须要加上 127(即 固定指数偏移量) 后才能存储 !

  3. mantissa:尾数使用 23 位来存储,其中尾数中的 “1.” ,不存储,目的就是为了节约存储空间(因为二进制的科学计数法中的整数部分始终都是为1 !尾数的整数部分已经形成了固化为 1,故这个尾数部分的整数部分的 1 称之为 “恒” )

  4. Notice:指数取值数据范围的上限和下限:为什么指数的偏移量值的范围是 -126 ~ 127呢?而不是 -128到 127呢?刚开始我也有些疑惑,终于通过各种手段找到了合理而理想的答案,因为8位所能表示的数值一共就只有255个,而在8位中,只有7位有效位数(不表示数值的那一位叫做符号位)由于正数最大只能表示到127, 负数最大只能表示到 -128 ,故指数取值数据范围包括正负数(-126 ~ 127),由于当阶码为0和255的时候后表示存储特殊值,那么实际浮点数的阶码表示范围就是 1 - 254 ,减去固定指数偏移量127 后实际得到指数取值表示范围就是 -126(下限) ~ 127(上限);计算机中的硬件已经决定了。设想一下,如果最小指数是-127,按E= 指数 + 固定指数偏移量(127),则-127 + 127 = 0, E = 0, 就说明存在非规格化数,这显然不符合IEEE 754标准的设计理念,也违背了IEEE 754 设计之初的初衷, 故指数最下限取 -126,最大上限取 127,这也符合了 E 的表示范围!127 这个固定指数偏移量是IEEE 754 浮点数算术标准规定好的!

  5. 比如:如果指数为 128(特殊指数) 加上固定指数偏移量 127 就得到 255了,这时候根据 IEEE 754的标准,255 阶码 这是表示一个特殊的数,如果这个数的尾数全部都为0时,(以单精度为例)符号位为 0,表示这个浮点数正无穷大 +∞ 二进制表示形式 
    
    0 1111 1111 00000000000000000000000 = Infinity

    ,反之亦然,如果是负无穷大,-∞ 则符号位为 1;

    
    1 1111 1111 00000000000000000000000 = -Infinity
  6. 比如:如果指数为 -127(特殊指数) 加上固定指数偏移量127 就得到 0 了,这时候根据 IEEE 754的标准, 0 阶码是表示一个特殊的数,如果这个数的阶码,尾数全部都是0时,(以单精度为例),符号位为0,表示这个数就是0.0本身!

     符号位为 0 时则代表这个数就是 0.0
    0 0000 0000 00000000000000000000000 = 0.0 符号位为 1时则代表这个数是 -0.0
    1 0000 0000 00000000000000000000000 = -0.0 (在数学中不存在-0.0这样的表数方式,在计算机中这样表示是二进制的特性所决定的,相对来说,比较方便,
    0.0 本身就等于 -0.0 ,因此不要拿 0.0 和 -0.0 做比较!,除了等于是 true, 其他都是 false !)

    如果指数为 0000 0000 时,尾数不为0 时,则表示这个数是弱规范数(就是 非规格化数 的另一个名字)

    比如:0 0000 0000 01010001001001010010011 = 3.726001E-39再比如:1 0000 0000 01010001001001010010011 = -3.726001E-39再比如 Java 中 float 单精度浮点数类型的 最小的弱规范数(即 非规格化数)的最小值(这是float能表示的最小值)是:0 0000 0000 00000000000000000000001 = 1.4E-451 0000 0000 00000000000000000000001 = -1.4E-45

float类型(单精度)的33.14:

就以33.14来分解:

先直接得到 33 的二进制数(符号位0可省略,float类型占用的4个字节中 IEEE 754标准规定了 特定有1位符号位来表示正负数!): 00 10 0001

小数部分是0.14:让小数一直乘以底数(或者叫基数也行),二进制的底数就是2,小于1则用结果继续乘,大于1则用结果减1继续乘,等于1则结束。


0.14 * 2 = 0.28 // 结果小于1,继续乘底数
0.28 * 2 = 0.56 // 结果小于1,继续乘底数
0.56 * 2 = 1.12 // 结果大于1,则减1继续乘底数
0.12 * 2 = 0.24 // 结果小于1,继续乘底数0.24 * 2 = 0.48 // 结果小于1,继续乘底数
0.48 * 2 = 0.96 // 结果小于1,继续乘底数
0.96 * 2 = 1.92 // 结果大于1,则减1继续乘底数
0.92 * 2 = 1.84 // 结果大于1,则减1继续乘底数0.84 * 2 = 1.68 // 结果大于1,则减1继续乘底数
0.68 * 2 = 1.36 // 结果大于1,则减1继续乘底数
0.36 * 2 = 0.72 // 结果小于1,继续乘底数
0.72 * 2 = 1.44 // 结果大于1,则减1继续乘底数0.44 * 2 = 0.88 // 结果小于1,继续乘底数
0.88 * 2 = 1.76 // 结果大于1,则减1继续乘底数
0.76 * 2 = 1.52 // 结果大于1,则减1继续乘底数
0.52 * 2 = 1.04 // 结果大于1,则减1继续乘底数0.04 * 2 = 0.08 // 结果小于1,继续乘底数
0.08 * 2 = 0.16 // 结果小于1,继续乘底数
0.16 * 2 = 0.32 // 结果小于1,继续乘底数
0.32 * 2 = 0.64 // 结果小于1,继续乘底数0.64 * 2 = 1.28 // 结果大于1,则减1继续乘底数(与第二行相同,这样就会一致死循环下去)
0.28 * 2 = 0.56 // 结果小于1,继续乘底数……将小数部分相乘的结果拼接起来,所以 float 类型的 0.14的二进制数表示形式为: 0 010 0011 1101 0111 0000 1 010 0011 1101 ……

(如果小数位是

  1. 比如小数位是 2^-n(不包括2^0和2^-0),

    1. 比如小数位是 :2^-1 = 0.5,2^-2=0.25,2^-3 = 0.125,2^-4 = 0.0625,2^-5 =  0.03125 ……(最终结果就是1.0)

  2. 比如小数位是 2^-4+ 的小数保留个位0(由高向低除开0)的小数,

    1. 比如小数位是:2^-4 = 0.0625 (小数位是0.625),

    2. 2^-5 =  0.03125 (小数位是0.3125),

    3. 2^-6= 0.015625(小数位是0.15625),

    4. 2^-7= 0.0078125(小数位是0.78125),

    5. 2^-8= 0.00390625(小数位是0.390625),

    6. 2^-9= 0.001953125(小数位是0.1953125)……(最终结果还是1.0)

  3. 比如小数位是 小数位乘以 底数(2)的结果 是 (1.(2^-n))/(2^n)的结果:

    1. 比如(小数位是0.75,那么小数位乘底数的结果就是1.5,大于1减一继续乘底数,那么最终结果就是 1.0,在小数位乘以底数的结果不等于1的情况下,小数位的结果就是(1.(2^-1))/(2^1) = 0.75 ),

    2. (小数位是0.5625,那么小数位乘以底数的结果就是 0.5625 底数的结果就是1.125,大于1减一继续乘底数,那么最终结果就是1.0,在小数位乘以底数的结果不等于1的情况下,小数位的结果就是(1.(2^-3))/(2^1) = 0.5625 ),

    3. (小数位是0.078125,那么小数位乘以底数的结果就是 0.078125 * 2 * 2 * 2 * 2 = 1.25,大于1减一继续乘底数,那么最终结果就是1.0,在小数位乘以底数的结果不等于1的情况下,小数位的结果就是(1.(2^-2))/(2^4) = 0.078125 ),

    4. (小数位是0.2578125,那么小数位乘以底数的结果就是 0.2578125* 2 * 2   = 1.03125,大于1减一继续乘底数,那么最终结果就是1.0,在小数位乘以底数的结果不等于1的情况下,小数位的结果就是(1.(2^-5))/(2^2)= 0.2578125 ),

    5. (小数位是0.1328125,那么小数位乘以底数的结果就是 0.1328125 * 2 * 2 * 2  = 1.0625,大于1减一继续乘底数,那么最终结果就是1.0,在小数位乘以底数的结果不等于1的情况下,小数位的结果就是(1.(2^-2))/(2^3)= 0.03125 )……

  4. 比如小数位是 小数位乘以底数(2)的结果 是 (1.(2^-4+ 小数保留个位 0(由高向低除开0)的小数)/(2^n)的结果 :

    1. 比如:(小数位是0.4453125,那么小数位乘以底数的结果就是0.4453125 * 2 * 2 = 1.78125,大于1减一继续乘底数,那么最终结果就是1.0 ,在小数位乘以底数的结果小于1的情况下,小数位的结果就是 (1.(2^-7)/(2^2) = 0.4453125),

    2. (小数位是0.34765625,那么小数位乘以底数的结果就是0.34765625 * 2 * 2 = 1.390625,大于1减一继续乘底数,那么最终结果就是1.0 ,在小数位乘以底数的结果小于1的情况下,小数位的结果就是 (1.(2^-8))/(2^2)= 0.34765625)

    3. (小数位是0.14453125,那么小数位乘以底数的结果就是0.14453125* 2 * 2 * 2 * 2 = 1.15625,大于1减一继续乘底数,那么最终结果就是1.0 ,在小数位乘以底数的结果小于1的情况下,小数位的结果就是 (1.(2^-6))/(2^4)= 0.14453125),

    4. (小数位是0.1640625,那么小数位乘以底数的结果就是0.1640625 * 2 * 2 * 2  = 1.3125,大于1减一继续乘底数,那么最终结果就是1.0 ,在小数位乘以底数的结果小于1的情况下,小数位的结果就是 (1.(2^-5))/(2^3)= 0.1640625)……

)(或者浮点数是 xxx.0)就会比较简单:)(但是如果小数位乘以底数的(次数)长度 大于了 尾数 所能表示的位数 ,都是会造成不精确的浮点数,(浮点数只是一个近似值,并不是一个绝对值!)flaot 和double都是一样!)

比如: 0.0625 只需要乘4次:(因为0.0625是2的负4次方积)(即 (2^0)= 1 / (2^4) = 2^-4 = 0.0625 )


0.0625 * 2 = 0.125 // 小于1,继续乘底数
0.125 * 2 = 0.25 // 小于1,继续乘底数
0.25 * 2 = 0.5 // 小于1,继续乘底数
0.5 * 2 = 1.0 // 等于1,结束将小数部分相乘的结果拼接起来,0.0625的二进制完整表示形式为:0. 0001
二进制的科学计数法表示为 : 1.0e-4 (或者 1.e-4 或者 1e-4)

科学计数法表示float类型(单精度)的二进制小数:

把 33.14 的 整数二进制和小数部分二进制拼接起来,采用科学计数法来表示二进制小数

33.14 = 00 10 0001.0 010 0011 1101 0111 0000 1 010 0011 1101 ……(符号位不要,存储的时候已经有一个 bit 表示!)

底数: 因为是二进制小数,所以这里的底数是 2 ,不是十进制小数,(这里使用 E或者e 来表示底数 2)

转换成科学计数法即:

10 0001.0 010 0011 1101 0111 0000 1 010 0011 1101 …… = 1.00001 0 0100 0111 1010 1110 0 001 0100 0111 101……*E+5

= 1.0000 1001 0001 1110 1011 100 * E+5 (取23位尾数就够了!)

指数:会看到这里的float的33.14的二进制小数的科学计数法指数 为5,

打个比方:

(把0.1640625的二进制位转换成二进制的科学计数法即:)(让小数一直乘以底数,二进制的底数就是2,小于1则用结果继续乘,大于1则用结果减1继续乘,等于1则结束。)

0.1640625 * 2 = 0.328125 // 小于1,继续乘底数
0.328125 * 2 = 0.65625 // 小于1,继续乘底数
0.65625 * 2 = 1.3125; // 大于1,减一继续乘底数
0.3125 * 2 = 0.625; //  小于1,继续乘底数
0.625 * 2 = 1.25; // 大于1,减一继续乘底数
0.25 * 2 = 0.5; // 小于1,继续乘底数
0.5 * 2 = 1.0 // 等于1,结束

0.1640625的整数和小数拼接起来转换成二进制就是就是: 0 0010101 = 1.0101E-3 ;

所以0.1640625的二进制的小数科学计数法指数 就是 -3


按照IEEE 754标准存储 float (单精度)浮点数:

IEEE 754的标准规定存储格式:即:

规格化数 = 符号位(sign) X  阶码(exponent)X 尾数(mantissa)

或者 N = (-1)S X 2E-127  X 1.M

单精度浮点数(Java中 float 类型)在存储时是占用的4个字节,即32位 , 符号位和阶码和尾数的格式如图所示:

  float类型   32位

说明:

  1. sign: 用 1 位来表示符号位,若浮点数是正数,则符号位为 “0”,若是负数,则符号位为 “1”

  2. exponent: 用 8 位来表示存储的阶码,存储的阶码等于规格化数中的指数加上127(固定指数偏移量值),即 阶码 = 指数 + 127(固定指数偏移量值), 8位表示的存储数值容量可以是255个数字(2^8-1、2的八次方-1(减-就是减的那个符号位)),指数取值数据范围包括正负数就是(-126 ~ 127),由于指数可能是负数,为了处理负指数的情况,IEEE 754 要求指数必须要加上 127(即 固定指数偏移量) 后才能存储 !

  3. mantissa:尾数使用 23 位来存储,其中尾数中的 “1.” ,不存储,目的就是为了节约存储空间(因为二进制的科学计数法中的整数部分始终都是为1 !尾数的整数部分已经形成了固化为 1,故这个尾数部分的整数部分的 1 称之为 “恒” )

  4. Notice:指数取值数据范围的上限和下限:为什么指数的偏移量值的范围是 -126 ~ 127呢?而不是 -128到 127呢?刚开始我也有些疑惑,终于通过各种手段找到了合理而理想的答案,因为8位所能表示的数值一共就只有255个,而在8位中,只有7位有效位数(不表示数值的那一位叫做符号位)由于正数最大只能表示到127, 负数最大只能表示到 -128 ,故指数取值数据范围包括正负数(-126 ~ 127),由于当阶码为0和255的时候后表示存储特殊值,那么实际浮点数的阶码表示范围就是 1 - 254 ,减去固定指数偏移量127 后实际得到指数取值表示范围就是 -126(下限) ~ 127(上限);计算机中的硬件已经决定了。设想一下,如果最小指数是-127,按E= 指数 + 固定指数偏移量(127),则-127 + 127 = 0, E = 0, 就说明存在非规格化数,这显然不符合IEEE 754标准的设计理念,也违背了IEEE 754 设计之初的初衷, 故指数最下限取 -126,最大上限取 127,这也符合了 E 的表示范围!127 这个固定指数偏移量是IEEE 754 浮点数算术标准规定好的!(看单精度浮点数举例请往上翻

    Java入门之7:Java中的float和double类型的浮点数是怎么按照IEEE 754标准存储的?相关推荐

    1. 【编程基础】浮点数在计算机中的存储 —— IEEE 754标准

      寻求更好的阅读体验,请移步 :浮点数在计算机中的存储 -[Mculover666的个人博客]. 用于存储小数的数据类型是有单精度浮点型(float)和双精度浮点型(double),那么,浮点数在计算机 ...

    2. 用Java向SQL Server数据库中插入float数据报错An error occurred while converting the Float value to JDBC data type

      作者:翁松秀 用Java向SQL Server数据库中插入float数据报错 用Java向SQL Server数据库中插入float数据报错 报错信息: 报错原因: 解决方案: 报错信息: An er ...

    3. java float的精度_java中的float和double的精度问题

      此文解释了为何float的范围比int大(同样4字节),但有些int是float无法正确表达的(精度丢失) java中的float和double的精度问题 1.背景知识 在java中没有细讲,只是讲了 ...

    4. java中float和double型数据在赋值时有哪些注意事项?,java语言中float和double类型的数据在编程时的注意事项...

      float和double类型的数据在编程时的需要注意的地方 package execisetest; public class AccuranceTest {     public static vo ...

    5. C#中对于float,double,decimal的误解

      C#中对于float,double,decimal的误解 原文:C#中对于float,double,decimal的误解 一直很奇怪C#的预定义数据类型中为什么加了一个decimal,有float和d ...

    6. mysql double 存储_关于MYSQL中FLOAT和DOUBLE类型的存储-阿里云开发者社区

      关于MYSQL中FLOAT和DOUBLE类型的存储 重庆八怪 2016-04-12 844浏览量 简介: 关于MYSQL中FLOAT和DOUBLE类型的存储 其实在单精度和双精度浮点类型存储中其存储方 ...

    7. 关于MYSQL中FLOAT和DOUBLE类型的存储

      关于MYSQL中FLOAT和DOUBLE类型的存储 其实在单精度和双精度浮点类型存储中其存储方式和C/C++一致准守IEEE标准他们都是浮点型的,所谓的浮点型,是小数点的位置可变,其能够表示的范围比定 ...

    8. 深入理解C++浮点数(float、double)类型数据比较、相等判断

      深入理解C++浮点数(float.double)类型数据比较.相等判断 浮点数在内存中的存储机制和整型数不同,其有舍入误差,在计算机中用近似表示任意某个实数.具体的说,这个实数由一个整数或定点数(即尾 ...

    9. MATLAB中的数据从double类型强制转化为uint8其舍入用的是四舍五入,附MATLAB基本数据类型

      MATLAB中的数据从double类型强制转化为uint8其舍入用的是四舍五入, 例子代码如下: A=[0.1 0.2 0.3;0.4 0.5 0.6;0.7 0.8 0.9]; B=uint8(A) ...

    最新文章

    1. 2D池化IPoolingLayer
    2. 用Python进行诗歌接龙
    3. 进阶必备:素数筛法(欧拉,埃氏筛法)
    4. linux mysql 日志乱码_Linux下MySQL保存进去数据为乱码的解决办法
    5. leetcode算法题--单词拆分★
    6. 全球及中国碳化硅 (SiC) 肖特基二极管行业竞争潜力与供应规划研究报告2022版
    7. BS作业 基于springboot + Thymeleaf +mybatis 实现的书城管理系统
    8. leetcode —— 16. 最接近的三数之和
    9. realvnc 6 教程 linux,CentOS 6下VNC的安装与配置
    10. 英特尔处理器接连爆出漏洞,Intel:这次不打算修了
    11. 大数据分析过程中包含哪些技术
    12. VS Code 调试 Angular 和 TypeScript 的配置
    13. Hitool网口烧写失败问题
    14. 机架服务器如何使用无线网卡,软路由加装老旧无线网卡
    15. 服务器的显示器无信号怎么解决办法,显示器无信号怎么办?显示器无信号解决办法大全...
    16. python — numpy计算矩阵特征值,特征向量
    17. CentOS 7.6 编译安装最新版本glibc2.30 实录
    18. mixin(公共样式定义)
    19. 字符串_字符串的复制
    20. Neo4j学习(2)--简单入门

    热门文章

    1. C++课程设计(校车订票系统)
    2. 解决360篡改谷歌或其他浏览器主页的问题(亲身经历绝不copy)
    3. 阿里云Linux服务器部署Mysql,JDK以及Tomcat教程
    4. Flutter版讯飞语音识别demo
    5. arcgis 圈选获取图层下点位_关于Arcgis这62个常用技巧,你造吗
    6. 小程序开发实战学习笔记 1
    7. ARM基础(5) ARM通讯接口
    8. ICMP目的网络,主机,协议,端口不可达报文的Type值,Code值分别是
    9. 使用GOOGLE API做了个简繁英互译小工具
    10. mini.DataGrid使用说明