Java入门之7:Java中的float和double类型的浮点数是怎么按照IEEE 754标准存储的?
前言:
这篇博文,我写了好几天……,里面涉及的基础概念比较多,内容比较多,举例也比较多,想搞清楚明白就难免 我自己都会觉得啰嗦,我整理了目录出来,看完需要一点时间,可以自行根据需要、感兴趣的 选择阅读、浏览,如果你有耐心也可以全部看完,哈哈哈……
目录
浮点数概念:
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位
说明:
sign: 用 1 位来表示符号位,若浮点数是正数,则符号位为 “0”,若是负数,则符号位为 “1”
exponent: 用 8 位来表示存储的阶码,存储的阶码等于规格化数中的指数加上127(固定指数偏移量值),即 阶码 = 指数 + 127(固定指数偏移量值), 8位表示的存储数值容量可以是255个数字(2^8-1、2的八次方-1(减-就是减的那个符号位)),指数取值数据范围包括正负数就是(-126 ~ 127),由于指数可能是负数,为了处理负指数的情况,IEEE 754 要求指数必须要加上 127(即 固定指数偏移量) 后才能存储 !
mantissa:尾数使用 23 位来存储,其中尾数中的 “1.” ,不存储,目的就是为了节约存储空间(因为二进制的科学计数法中的整数部分始终都是为1 !尾数的整数部分已经形成了固化为 1,故这个尾数部分的整数部分的 1 称之为 “恒” )
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 浮点数算术标准规定好的!
- 比如:如果指数为 128(特殊指数) 加上固定指数偏移量 127 就得到 255了,这时候根据 IEEE 754的标准,255 阶码 这是表示一个特殊的数,如果这个数的尾数全部都为0时,(以单精度为例)符号位为 0,表示这个浮点数正无穷大 +∞ 二进制表示形式
0 1111 1111 00000000000000000000000 = Infinity
,反之亦然,如果是负无穷大,-∞ 则符号位为 1;
1 1111 1111 00000000000000000000000 = -Infinity
比如:如果指数为 -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 ……
(如果小数位是
比如小数位是 2^-n(不包括2^0和2^-0),
比如小数位是 :2^-1 = 0.5,2^-2=0.25,2^-3 = 0.125,2^-4 = 0.0625,2^-5 = 0.03125 ……(最终结果就是1.0)
比如小数位是 2^-4+ 的小数保留个位0(由高向低除开0)的小数,
比如小数位是:2^-4 = 0.0625 (小数位是0.625),
2^-5 = 0.03125 (小数位是0.3125),
2^-6= 0.015625(小数位是0.15625),
2^-7= 0.0078125(小数位是0.78125),
2^-8= 0.00390625(小数位是0.390625),
2^-9= 0.001953125(小数位是0.1953125)……(最终结果还是1.0)
比如小数位是 小数位乘以 底数(2)的结果 是 (1.(2^-n))/(2^n)的结果:
比如(小数位是0.75,那么小数位乘底数的结果就是1.5,大于1减一继续乘底数,那么最终结果就是 1.0,在小数位乘以底数的结果不等于1的情况下,小数位的结果就是(1.(2^-1))/(2^1) = 0.75 ),
(小数位是0.5625,那么小数位乘以底数的结果就是 0.5625 底数的结果就是1.125,大于1减一继续乘底数,那么最终结果就是1.0,在小数位乘以底数的结果不等于1的情况下,小数位的结果就是(1.(2^-3))/(2^1) = 0.5625 ),
(小数位是0.078125,那么小数位乘以底数的结果就是 0.078125 * 2 * 2 * 2 * 2 = 1.25,大于1减一继续乘底数,那么最终结果就是1.0,在小数位乘以底数的结果不等于1的情况下,小数位的结果就是(1.(2^-2))/(2^4) = 0.078125 ),
(小数位是0.2578125,那么小数位乘以底数的结果就是 0.2578125* 2 * 2 = 1.03125,大于1减一继续乘底数,那么最终结果就是1.0,在小数位乘以底数的结果不等于1的情况下,小数位的结果就是(1.(2^-5))/(2^2)= 0.2578125 ),
(小数位是0.1328125,那么小数位乘以底数的结果就是 0.1328125 * 2 * 2 * 2 = 1.0625,大于1减一继续乘底数,那么最终结果就是1.0,在小数位乘以底数的结果不等于1的情况下,小数位的结果就是(1.(2^-2))/(2^3)= 0.03125 )……
比如小数位是 小数位乘以底数(2)的结果 是 (1.(2^-4+ 小数保留个位 0(由高向低除开0)的小数)/(2^n)的结果 :
比如:(小数位是0.4453125,那么小数位乘以底数的结果就是0.4453125 * 2 * 2 = 1.78125,大于1减一继续乘底数,那么最终结果就是1.0 ,在小数位乘以底数的结果小于1的情况下,小数位的结果就是 (1.(2^-7)/(2^2) = 0.4453125),
(小数位是0.34765625,那么小数位乘以底数的结果就是0.34765625 * 2 * 2 = 1.390625,大于1减一继续乘底数,那么最终结果就是1.0 ,在小数位乘以底数的结果小于1的情况下,小数位的结果就是 (1.(2^-8))/(2^2)= 0.34765625)
(小数位是0.14453125,那么小数位乘以底数的结果就是0.14453125* 2 * 2 * 2 * 2 = 1.15625,大于1减一继续乘底数,那么最终结果就是1.0 ,在小数位乘以底数的结果小于1的情况下,小数位的结果就是 (1.(2^-6))/(2^4)= 0.14453125),
(小数位是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位
说明:
sign: 用 1 位来表示符号位,若浮点数是正数,则符号位为 “0”,若是负数,则符号位为 “1”
exponent: 用 8 位来表示存储的阶码,存储的阶码等于规格化数中的指数加上127(固定指数偏移量值),即 阶码 = 指数 + 127(固定指数偏移量值), 8位表示的存储数值容量可以是255个数字(2^8-1、2的八次方-1(减-就是减的那个符号位)),指数取值数据范围包括正负数就是(-126 ~ 127),由于指数可能是负数,为了处理负指数的情况,IEEE 754 要求指数必须要加上 127(即 固定指数偏移量) 后才能存储 !
mantissa:尾数使用 23 位来存储,其中尾数中的 “1.” ,不存储,目的就是为了节约存储空间(因为二进制的科学计数法中的整数部分始终都是为1 !尾数的整数部分已经形成了固化为 1,故这个尾数部分的整数部分的 1 称之为 “恒” )
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标准存储的?相关推荐
- 【编程基础】浮点数在计算机中的存储 —— IEEE 754标准
寻求更好的阅读体验,请移步 :浮点数在计算机中的存储 -[Mculover666的个人博客]. 用于存储小数的数据类型是有单精度浮点型(float)和双精度浮点型(double),那么,浮点数在计算机 ...
- 用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 ...
- java float的精度_java中的float和double的精度问题
此文解释了为何float的范围比int大(同样4字节),但有些int是float无法正确表达的(精度丢失) java中的float和double的精度问题 1.背景知识 在java中没有细讲,只是讲了 ...
- java中float和double型数据在赋值时有哪些注意事项?,java语言中float和double类型的数据在编程时的注意事项...
float和double类型的数据在编程时的需要注意的地方 package execisetest; public class AccuranceTest { public static vo ...
- C#中对于float,double,decimal的误解
C#中对于float,double,decimal的误解 原文:C#中对于float,double,decimal的误解 一直很奇怪C#的预定义数据类型中为什么加了一个decimal,有float和d ...
- mysql double 存储_关于MYSQL中FLOAT和DOUBLE类型的存储-阿里云开发者社区
关于MYSQL中FLOAT和DOUBLE类型的存储 重庆八怪 2016-04-12 844浏览量 简介: 关于MYSQL中FLOAT和DOUBLE类型的存储 其实在单精度和双精度浮点类型存储中其存储方 ...
- 关于MYSQL中FLOAT和DOUBLE类型的存储
关于MYSQL中FLOAT和DOUBLE类型的存储 其实在单精度和双精度浮点类型存储中其存储方式和C/C++一致准守IEEE标准他们都是浮点型的,所谓的浮点型,是小数点的位置可变,其能够表示的范围比定 ...
- 深入理解C++浮点数(float、double)类型数据比较、相等判断
深入理解C++浮点数(float.double)类型数据比较.相等判断 浮点数在内存中的存储机制和整型数不同,其有舍入误差,在计算机中用近似表示任意某个实数.具体的说,这个实数由一个整数或定点数(即尾 ...
- 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) ...
最新文章
- 2D池化IPoolingLayer
- 用Python进行诗歌接龙
- 进阶必备:素数筛法(欧拉,埃氏筛法)
- linux mysql 日志乱码_Linux下MySQL保存进去数据为乱码的解决办法
- leetcode算法题--单词拆分★
- 全球及中国碳化硅 (SiC) 肖特基二极管行业竞争潜力与供应规划研究报告2022版
- BS作业 基于springboot + Thymeleaf +mybatis 实现的书城管理系统
- leetcode —— 16. 最接近的三数之和
- realvnc 6 教程 linux,CentOS 6下VNC的安装与配置
- 英特尔处理器接连爆出漏洞,Intel:这次不打算修了
- 大数据分析过程中包含哪些技术
- VS Code 调试 Angular 和 TypeScript 的配置
- Hitool网口烧写失败问题
- 机架服务器如何使用无线网卡,软路由加装老旧无线网卡
- 服务器的显示器无信号怎么解决办法,显示器无信号怎么办?显示器无信号解决办法大全...
- python — numpy计算矩阵特征值,特征向量
- CentOS 7.6 编译安装最新版本glibc2.30 实录
- mixin(公共样式定义)
- 字符串_字符串的复制
- Neo4j学习(2)--简单入门
热门文章
- 【编程基础】浮点数在计算机中的存储 —— IEEE 754标准