赞
踩
IEEE 二进制浮点数算术标准(IEEE 754)是 20 世纪 80 年代以来最广泛使用的浮点数运算标准,为许多 CPU 与浮点运算器所采用。IEEE754 标准提供了如何在计算机内存中,以二进制的方式存储十进制浮点数的具体标准。IEEE754 标准发布于 1985 年,包括 javascript,Java,C 在内的许多编程语言在实现浮点数时,都遵循 IEEE754 标准。
那么问题来了,怎么存储浮点数?在探究浮点数怎么存储之前先看看整数是如何存储的。
大家知道,整数包括负数,零,和正数。计算机中的整数分为有符号数和无符号数。
有符号数:最高位表示符号,即最高位为 0,表示正数,最高位为 1,表示负数。如果用 N 位来表示整数,那么有符号数的范围为:[-2(N-1),2(N-1)-1]。用 8 位来表示有符号整数数,由于第 8 位用于表示了符号,因此,整数的表示范围为[-128,+127]。
无符号数:表示非负数,整个位数都用来表示整数的值。如果用 N 位来表示整数,无符号数的表示范围为[0,2N-1]。用 8 位来表示有符号整数数,则无符号数的表示范围为[0,255]。
整数的编码分为原码、反码、和补码。计算里使用的是补码的存储方式。它们的定义如下:
原码:在数值前面增加了一位符号位(即最高位为符号位),该位为 0 表示正数,该位为 1 表示负数,其余位表示数值的大小。
反码:正数的反码与其原码相同。负数的反码是对其原码逐位取反,但符号位除外。
补码:正数的补码与其原码相同,负数的补码就是对该负数的反码加 1。
如果用一字节存储整数(有符号)
数值 | 补码 | 反码 | 原码 |
---|---|---|---|
127 | 0111 1111 | 0111 1111 | 0111 1111 |
0 | 0000 0000 | 0000 0000 | 0000 0000 |
-1 | 1111 1111 | 1111 1110 | 1000 0001 |
-127 | 1000 0001 | 1000 0000 | 1111 1111 |
-128 | 1000 0000 |
注意:-128 没有相对应的原码和反码,-128 的补码为:10000000。
那么有了原码,计算机为什么还要用补码呢?
来看看它们的运算情况。 假设字长为 8 位 ,那么原码的运算方式为: 1 - 1 = 1 + ( -1 ) =(00000001) + (10000001) = (10000010) = -2 ,这显然不正确。原码在两个整数的加法运算中是没有问题的,问题出现在带符号位的负数身上。 原码无法满足运算要求,因此对除符号位外的其余各位逐位取反就产生了反码。反码的取值空间和原码相同且一一对应。下面是反码的减法运算: 1 – 2 = 1 + ( -2 ) = (00000001) + (11111101) = (11111110) = ( -1 ) 正确。1 - 1 = 1 + ( -1 )= (00000001) + (11111110) = (11111111) = ( -0 ) 貌似没问题,其实有问题。反码的问题出现在(+0)和(-0)上,因为在人们的计算概念中零是没有正负之分的。 再来看补码的加减运算如下: 1 - 1 = 1 + (-1) = (00000001) + (11111111) = (00000000) = 0 正确。 1 – 2 = 1 + (-2) = (00000001) + (11111110) = (11111111) = ( -1 ) 正确。
补码的设计目的
⑴ 使符号位能与有效值部分一起参加运算,从而简化运算规则。
⑵ 使减法运算转换为加法运算,进一步简化计算机中运算器的线路设计。
此外,在补码中用-128 代替了-0,所以没有+0 和 -0 之分。
总结:原码无法满足减法运算、反码区分不了+0、-0,于是就有补码了
在计算机科学中,浮点(英语:floating point,缩写为 FP)是一种对于实数的近似值数值表现法,由一个有效数字(即尾数 M)加上幂数(E)来表示,通常是乘以某个基数的整数次指数得到。以这种表示法表示的数值,称为浮点数(floating-point number)。利用浮点进行运算,称为浮点计算,这种运算通常伴随着因为无法精确表示而进行的近似或舍入。
整数是用补码存储的,那么浮点数是用什么方式存储的呢?
答案:IEEE754 标准中浮点数是用**二进制的“科学计数法”**来存储的
科学记数法,是不是很熟悉,没错,就是它
科学记数法是一种记数的方法。把一个数表示成 a 与 10 的 n 次幂相乘的形式(1≤|a|<10,a 不为分数形式,n 为整数),这种记数法叫做**科学记数法。**当我们要标记或运算某个较大或较小且位数较多时,用科学记数法免去浪费很多空间和时间。
一个数(X)可以这样表示:
规约形式的浮点数
一个浮点数 (X) 可以这样表示:
如果浮点数的指数偏移值的编码在[1, 2e - 2]之间(例如,单精度浮点数,其指数偏移值区间为 [1, 254]),且在科学表示法的表示方式下,分数部分最高有效位(即整数字)是 1,那么这个浮点数将被称为规约形式的浮点数。“规约”是指用唯一确定的浮点形式去表示一个值。
这个浮点数在计算机中之需要存储 S、E、F 即可。
⚠️注意:[1, 2e - 2]中的e指的是指数位的长度,单精度指数位长度为8。
浮点数存储格式有哪些?
IEEE 754 规定了四种表示浮点数值的方式:单精确度(32 位)、双精确度(64 位)、延伸单精确度(43 比特以上,很少使用)与延伸双精确度(79 比特以上,通常以 80 位实现)。这里只介绍单精确度和双精确度。
IEEE754 标准中,单精度二进制小数,使用 32 个比特存储。
比特长度: | 1 | 8 | 23 |
---|---|---|---|
名称: | S | Exp | Fraction |
比特编号: | 31 | 30 至 23 偏正值(实际的指数大小+127) | 22 至 0 位编号(从右边开始为 0) |
IEEE754 标准中,双精度二进制小数,使用 64 个比特存储。
比特长度: | 1 | 11 | 52 |
---|---|---|---|
名称: | S | Exp | Fraction |
比特编号: | 63 | 62 至 52 偏正值(实际的指数大小+1023) | 51 至 0 位编号(从右边开始为 0) |
X:真值
S:符号位(0 为正、1 为负)
F:尾数位(小数点右边的尾数 F)
E:指数位( ⚠️ 注意: 指数位存储的不是指数(e),存储的是指数偏移值(指数+固定偏移值))
e:指数(指数为正,小数点右移,反之左移)
尾数:汉语词语,汉语拼音为 wěi shù;它有语文(现实生活之中语言文字)意义上及数学意义上的两大类含义。在数学里,有三种(自然数的个位数、有理数之中浮点数的小数部分、常用对数的尾数)含义;在现实生活中也指记账等号码的后几位,结算帐目中的小数目等。常用在手机号码、身份证号码等。
固定偏移值:32 位(单精度)浮点数固定偏移值为 127,64 位(双精度)浮点数的固定偏移值为 1023
指数偏移值:是指浮点数表示法中的指数位的编码值为指数的实际值加上某个固定的值(固定偏移值),IEEE 754 标准规定该固定值为 2e-1 - 1,其中的e 为存储指数的比特的长度。
⚠️ 注意:在这里我们把 1.F 称为小数或者分数,F 称为尾数。
问题与解答
问 1. 为什么尾数位存储的是 F,前面那个 1 不用存储了么?
问 2. 为什么指数位不直接存储指数,而是存储指数偏移值(指数+固定偏移值)?
问 3. 为什么固定偏移值为 2e-1 - 1?
问 4. 小朋友你是不是有很多问号???
答 1. 分数部分是 1.F,因为规格化的浮点数的分数部分最左位总是 1,一个确定的值存它干嘛,留那一位存储小数不香么,故这一位不予存储。
答 2,3. 如果直接存储指数,那么还要区分正负,留 1 比特作为符号位,太麻烦了。以单精度浮点数为例,指数位长度是 8 位,取值范围是 [0, 255]。但是科学计数法中的指数是可以为负数的,所以约定加上一个中间数 127,指数 [-127, 128] 与指数位的 [0, 255] 一一对应。
类别 | 正负号 | 实际指数 | 有偏移指数 | 指数域 | 尾数域 | 数值 |
---|---|---|---|---|---|---|
零 | 0 | -126 | 0 | 0000 0000 | 000 0000 0000 0000 0000 0000 | 0.0 |
负零 | 1 | -126 | 0 | 0000 0000 | 000 0000 0000 0000 0000 0000 | −0.0 |
1 | 0 | 0 | 127 | 0111 1111 | 000 0000 0000 0000 0000 0000 | 1.0 |
-1 | 1 | 0 | 127 | 0111 1111 | 000 0000 0000 0000 0000 0000 | −1.0 |
最小的非规约数 | * | -126 | 0 | 0000 0000 | 000 0000 0000 0000 0000 0001 | ±2−23 × 2−126 = ±2−149 ≈ ±1.4×10-45 |
中间大小的非规约数 | * | -126 | 0 | 0000 0000 | 100 0000 0000 0000 0000 0000 | ±2−1 × 2−126 = ±2−127 ≈ ±5.88×10-39 |
最大的非规约数 | * | -126 | 0 | 0000 0000 | 111 1111 1111 1111 1111 1111 | ±(1−2−23) × 2−126 ≈ ±1.18×10-38 |
最小的规约数 | * | -126 | 1 | 0000 0001 | 000 0000 0000 0000 0000 0000 | ±2−126 ≈ ±1.18×10-38 |
最大的规约数 | * | 127 | 254 | 1111 1110 | 111 1111 1111 1111 1111 1111 | ±(2−2−23) × 2127 ≈ ±3.4×1038 |
正无穷 | 0 | 128 | 255 | 1111 1111 | 000 0000 0000 0000 0000 0000 | +∞ |
负无穷 | 1 | 128 | 255 | 1111 1111 | 000 0000 0000 0000 0000 0000 | −∞ |
NaN | * | 128 | 255 | 1111 1111 | 非全 0 | NaN |
* 符号位可以为 0 或 1 . |
规约形式的浮点数( normal number )
如果浮点数的指数偏移值的编码在[1, 2e - 2]之间(例如,单精度浮点数,其指数偏移值区间为 [1, 254]),且分数大于等于 1 且小于 2。换句话说就是在科学表示法的表示方式下,分数部分最高有效位(即整数字)是 1。那么这个浮点数就是规约形式的浮点数。
以单精度为例,e = 8,则指数偏移值的编码在[1, 254]之间,指数的编码在[-126,127]之间,且尾数位任意(小数点前面有个 1,能确保分数大于等于 1 且小于 2)。那么最大的规约数,符号位为 0(表示正),尾数位为 111 1111 1111 1111 1111 1111,指数为 127,指数位为 1111 1110(254)。则最大的规约数的机器码为 0111 1111 0111 1111 1111 1111 1111 1111。二进制科学计数法为 1.111 1111 1111 1111 1111 1111 * 2127。转换成十进制约等于 3.402823e+38。那么最小的正规约数,符号位为 0(表示正),尾数位为 000 0000 0000 0000 0000 0000,指数为 -126,指数位为 0000 0001(-126)。则最小的正规约数的机器码为 0000 0000 1000 0000 0000 0000 0000 0000。二进制科学计数法为 1.000 0000 0000 0000 0000 0000 * 2-126。转换成十进制约等于 1.18×10-38。
由上所得,规约形势的浮点数取值范围为 [-3.4×1038, -1.18×10-38] U [1.18×10-38, 3.4×1038] 。
也就是说,使用规约形式的浮点数时,我们除了无法表示 0,也无法表示(0,1.18×10-38)之间的(靠近 0 的极小数)更无法表示大于3.4×1038的数。0、非规约形势的浮点数、∞就是解决这个问题的。
0
如果浮点数的指数偏移值的编码值是 0,尾数位前隐藏的整数部分是0. 而非 1.,并且尾数部分是 0。那么这个浮点数就是0。
指数的偏移值是 0,那么指数是-126,那么+0 的机器码为 0000 0000 0000 0000 0000 0000 0000 0000。
+0 的十进制为 0.0*2-126 = 0
⚠️ 注意: 0和非规约形式的浮点数中指数偏移值的计算方法有所不同,指数 = 1 - 固定偏移值
非规约形式的浮点数( denormal number )
如果浮点数的指数偏移值的编码值是 0,尾数位前隐藏的整数部分是0. 而非 1.,并且尾数部分非 0。那么这个浮点数就是非规约形式的浮点数。
尾数位的范围为[00000000000000000000001, 11111111111111111111111] 。那么最大的非规约数,符号位为 0(表示正),尾数位为 111 1111 1111 1111 1111 1111,指数为-126。则最大的非规约数的机器码为 0000 0000 0111 1111 1111 1111 1111 1111。二进制科学计数法为 0.111 1111 1111 1111 1111 1111 * 2-126。转换成十进制约等于 1.18×10-38。
由上所得,规约形势的浮点数取值范围为 [-1.18×10-38, -1.4×10-45] U [1.4×10-45, 1.18×10-38] 。
看看这个图,是不是明白了一点。
±∞
如果指数偏移值 = 2e - 1,并且尾数的小数部分是 0,这个数是 ±∞(同样和符号位相关)
指数的偏移值是 255,那么指数是 128,
那么+∞ 的机器码为 0111 1111 1000 0000 0000 0000 0000 0000
+∞ 的十进制为 1.0*2128 ≈ 3.4e38
NaN
如果指数偏移值 = 2e - 1,并且尾数的小数部分非 0,这个数表示为非数(NaN)
指数的偏移值是 255,那么指数是 128,
那么 NaN 的机器码为 0111 1111 1xxx xxxx xxxx xxxx xxxx xxxx(xxx xxxx xxxx xxxx xxxx xxxx 中最少有一个 1)
规格数、非规格数、特殊数有什么用呢?
如果你要往里面存储 4e38(这超过了最大的可取值), 32 位浮点数就会在内存中这样记录 “你存储的数超过了我的最大表示范围, 那我就记录你存储了一个无穷大…”
如果你要给一个负数开根号(如 √-1), 但是 ieee754 标准中的浮点数却不知道该怎么进行这个运算, 它就会在内存中这样记录 “不知道怎么算, 这不是个数值, 记录为 NaN”
单精度中三种数对应的指数位情况如下图。
最大正数
最大正数是指能表示的最大的数
因此双精度浮点数最大正数值的符号位 S=0,指数偏移值 E=2046,指数 e=2046-1023=1023,尾数 F=1111 … 1111(一共 52 个 1),其机器码为:0 11111111110 52 个 1。
MAX_VALUE=(−1)S×1.F×2e=+(1.52 个 1)×21023=1.7976931348623157e+308
那么最大正数值:Number.MAX_VALUE === 1.7976931348623157e+308
但是呢,并不能表示最大正数下面的所有整数
例如,当指数为 1023 时,小数点向右移动 1023 位,位数不够补零,记住只能补零,所以如果某个数需要补 1,那么无法实现,只能舍入,在计算机中存储一个和它比较接近的值,误差这就出现了。
最大安全数
最大安全数是指在此范围内的整数是精确的
如果指数最大是 52,那么小数点可以在尾数位的 52 位中来回移动。别忘了尾数位的 52 位可以为 1,也可以为 0,在加上小数点左边的那个 1,一共 53 个 1,所表示的数是 253 - 1。换句话说,双精度整数可以精确表示 1 -> 253 - 1 之间的整数。
Number.MAX_SAFE_INTEGER === 9007199254740991
首先精度是什么,浮点数的精度是指浮点数的有效数字的最大位数,从左边第一个不为 0 的数字开始的个数。
指数位的二进制位数决定浮点数的表示范围,尾数位的二进制位数决定浮点数的精度。以 64 位浮点数为例,尾数位有 52 位,加上规格化后小数点前隐藏的一位 1,那么浮点数以二进制表示的话精度是 53 位,53 位所能表示的最大数是 253 − 1 = 9,007,199,254,740,991 共 16 位,所以 double 最多能表示十进制有效数字 16 位,但绝对能保证的为 15 位,即 double 的十进制精度为 16 位。
总结,指数位决定范围,尾数位决定精度
0.75
[0.75]10 = [0.11]2 = [-10 * 1.1-1]
符号位 S = 0
指数 e = -1
指数位 E = e + 固定偏移值 = e + 127 = 126 = 0111 1110
小数位 F = 1,补 0 后的 F = 1000 0000 0000 0000 0000 000
0.75 的机器码如下:0.75 = [0011 1111 0100 0000 0000 0000 0000 0000 ]
-12.5
[-12.5]10 = [-1100.1]2 = [-11 * 1.10013]
符号位 S = 1
指数 e = 3
指数位 E = e + 固定偏移值 = e + 127 = 130 = 1000 0011
小数位 F = 1001,补 0 后的 F = 1001 0000 0000 0000 0000 000
-12.5 的机器码如下:-12.5 = [1100 0001 1100 1000 0000 0000 0000 0000 ]
关于浮点数十进制转换为二进制参考:https://www.cnblogs.com/xifenglou/p/9172015.html
浮点数的加减运算一般由以下五个步骤完成:对阶、尾数运算、规格化、舍入处理、溢出判断
所谓对阶是指将两个进行运算的浮点数的阶码(指数偏移值)对齐的操作。对阶的目的是为使两个浮点数的尾数能够进行加减运算。因为,当进行 1.Fx x 2Ex与 1.Fy x 2Ey加减运算时,只有使两浮点数的指数位部分相同,才能将相同的指数值作为公因数提出来,然后进行尾数的加减运算。对阶的具体方法是:首先求出两浮点数阶码的差,即 ⊿E = Ex - Ey,将小阶码加上 ⊿E,使之与大阶码相等,同时将小阶码对应的浮点数的尾数右移相应位数,以保证该浮点数的值不变。几点注意:
(1)对阶的原则是小阶对大阶,之所以这样做是因为若大阶对小阶,则尾数的数值部分的高位需移出,而小阶对大阶移出的是尾数的数值部分的低位,这样损失的精度更小。
(2)若 ⊿E = 0,说明两浮点数的阶码已经相同,无需再做对阶操作了。
(3)采用补码表示的尾数右移时,符号位保持不变。
(4)由于尾数右移时是将最低位移出,会损失一定的精度,为减少误差,可先保留若干移出的位,供以后舍入处理用。
尾数运算就是进行完成对阶后的尾数相加减。这里采用的纯小数的定点数加减运算。
在机器中,为保证浮点数表示的唯一性,浮点数在机器中都是以规格化形式存储的。对于 IEEE754 标准的浮点数来说,就是尾数必须是 1.F 的形式。由于在进行上述两个定点小数的尾数相加减运算后,尾数有可能是非规格化形式,为此必须进行规格化操作。
规格化操作包括左规和右规两种情况。
左规操作:将尾数左移,同时阶码减值,直至尾数成为 1.F 的形式。例如,浮点数 0.0011x25是非规格化的形式,需进行左规操作,将其尾数左移 3 位,同时阶码减 3,就变成 1.1100x22规格化形式了。
右规操作:将尾数右移 1 位,同时阶码增 1,便成为规格化的形式了。要注意的是,右规操作只需将尾数右移一位即可,这种情况出现在尾数的最高位(小数点前一位)运算时出现了进位,使尾数成为 10.xxxx 或 11.xxxx 的形式。例如,10.0011x25右规一位后便成为 1.00011x26的规格化形式了。
浮点运算在对阶或右规时,尾数需要右移,被右移出去的位会被丢掉,从而造成运算结果精度的损失。为了减少这种精度损失,可以将一定位数的移出位先保留起来,称为保护位,在规格化后用于舍入处理。
IEEE754 标准列出了四种可选的舍入处理方法:
(1)就近舍入(round to nearest)这是标准列出的默认舍入方式,其含义相当于我们日常所说的“四舍五入”。例如,对于 32 位单精度浮点数来说,若超出可保存的 23 位的多余位大于等于 100…01,则多余位的值超过了最低可表示位值的一半,这种情况下,舍入的方法是在尾数的最低有效位上加 1;若多余位小于等于 011…11,则直接舍去;若多余位为 100…00,此时再判断尾数的最低有效位的值,若为 0 则直接舍去,若为 1 则再加 1。
(2)朝+∞ 舍入(round toward +∞)对正数来说,只要多余位不为全 0,则向尾数最低有效位进 1;对负数来说,则是简单地舍去。
(3)朝-∞ 舍入(round toward -∞)与朝+∞ 舍入方法正好相反,对正数来说,只是简单地舍去;对负数来说,只要多余位不为全 0,则向尾数最低有效位进 1。
(4)朝 0 舍入(round toward 0)
即简单地截断舍去,而不管多余位是什么值。这种方法实现简单,但容易形成累积误差,且舍入处理后的值总是向下偏差。
与定点数运算不同的是,浮点数的溢出是以其运算结果的阶码的值是否产生溢出来判断的。若阶码的值超过了阶码所能表示的最大正数,则为上溢,进一步,若此时浮点数为正数,则为正上溢,记为+∞,若浮点数为负数,则为负上溢,记为-∞;若阶码的值超过了阶码所能表示的最小负数,则为下溢,进一步,若此时浮点数为正数,则为正下溢,若浮点数为负数,则为负下溢。正下溢和负下溢都作为 0 处理。
要注意的是,浮点数的表示范围和补码表示的定点数的表示范围是有所不同的,定点数的表示范围是连续的,而浮点数的表示范围可能是不连续的。
为什么 0.1 + 0.2 = 0.3000000000004?
推导
0.1>>>二进制>>>二进制科学记数法>>机器码[64 位](符号位+指数位+尾数位)
0.1>>>
0.0001100110011001100110011001100110011001100110011001101>>>
1.1001100110011001100110011001100110011001100110011010 * 2-4>>>
0011111110111001100110011001100110011001100110011001100110011010
0.2>>二进制>>>二进制科学记数法>>机器码[64 位](符号位+指数位+尾数位)
0.2>>>
0.001100110011001100110011001100110011001100110011001101>>>
1.1001100110011001100110011001100110011001100110011010 * 2-3>>>
0011111111001001100110011001100110011001100110011001100110011010
可以看出来在转换为二进制时
0.1 >>> 0.0001 1001 1001 1001…(1001 无限循环)
0.2 >>> 0.0011 0011 0011 0011…(0011 无限循环)
⚠️ 注意:这里在存储的时候已经发生了精度丢失
对阶
小阶对大阶则-4 向-3 对齐,则 0.1 的二进制科学记数法为
0.11001100110011001100110011001100110011001100110011010 * 2-3
⚠️ 注意:加粗的那个 0 是新补的,红色的那个 0 已经被移出
尾数运算
0.1100110011001100110011001100110011001100110011001101 +
1.1001100110011001100110011001100110011001100110011010 =
10.0110011001100110011001100110011001100110011001100111
结果规格化
运算结果规格话得到的结果为
1.00110011001100110011001100110011001100110011001100111* 2-2
此时尾数已经为 53 位了
舍入处理
1.00110011001100110011001100110011001100110011001100111* 2-2 =
1.0011001100110011001100110011001100110011001100110100*2-2
⚠️ 注意:红色的那个 1 会被舍入,这里第二次精度丢失
转换成十进制结果是:0.30000000000000004,结果并不等于 0.3。
溢出判断
浮点数的溢出其实是阶码的溢出表现出来的,在算术运算过程中要检查是否产生了溢出。若阶码正常,算术运算正常结束;若阶码溢出,则要进行相应处理。如上求和结果的阶码为 01111111101,没有产生溢出,因此运算结束。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。