浮点数精度

1 篇文章 0 订阅
计算机中浮点数的表示方式导致了如0.1+0.2不等于0.3的现象。本文深入探讨了浮点数的二进制小数表示和IEEE 754标准,解析了浮点数的符号位、阶码和尾数,并介绍了非规格化值、规格化值和特殊值的概念。通过对浮点数的加法运算过程的分析,揭示了精度误差产生的原因。
摘要由CSDN通过智能技术生成

当我们在JavaScript中进行一些浮点数运算时,有时候就可能会出现一些奇怪的的现象,就比如说下面这个非常简单的例子:

> 0.1+0.2
< 0.30000000000000004

0.1+0.2= ? 这是一个非常简单的问题,就算是一个小学生都能给出0.1+0.2=0.3的正确答案,可为什么对于最开始用于计算用途的计算机却给了我们一个错误的答案呢?其实不仅在JavaScript中会出现这样的错误的答案,在其他编程语言中也会有这样奇怪的现象。所以这应该就不是JavaScript中的缺陷,而是存在于计算机中的问题。
想要了解计算机为什么会给出这样一个答案,我们就要从计算机的底层原理说起,对于计算机来说,我们所给所有数据,比如说:整型,字符型,浮点数…计算机都将这些数据转化为二进制的数据。对于整型和字符型转换成二进制数据都比较简单,但浮点数却是一个特例。

二进制小数

在20世纪80年代之前,每个计算机制造商都涉及了自己的表示浮点数的规则,以及对浮点数执行运算的细节,但他们常常都不会去关注运算的精确性。但在1985年左右,这样的情况随着IEEE标准754的推出而改变了,这是一个仔细定制的表示浮点数及其运算的标准。接下来我们就来看看在IEEE浮点格式中是如何表示数字的。
理解浮点数的第一步就是考虑含有小数值的二进制数字。首先,我们来看看更为熟悉的十进制表示法。十进制表示法使用的表现形式为:dmdm-1···d1d0.d-1d-2···d-n其中每个十进制数di的取值范围为0~9。
数字权的定义于十进制小数点符号’.'相关,小数点左边的的数字的权是10的正幂,而小数点右边的数字的权是10的负幂,例如:12.3410表示数字1×101+2×100+3×10-1+4×10-2=12 34 100 \frac{34}{100} 10034
类似的考虑一个形如bmbm-1···b1b0.b-1b-2···b-n的表示法,其中bi的取值范围是0和1,同样点左边的位是2的正幂,点右边的位的权是2的负幂。101.112表示数字1×22+0×21+1×20+1×2-1+1×2-2=4+0+1+ 1 2 + \frac{1}{2}+ 21+ 1 4 \frac{1}{4} 41=5 3 4 \frac{3}{4} 43
我们假定编码的长度是有限的,那么十进制表示法就不能准确的表达像 1 3 \frac{1}{3} 31 1 5 \frac{1}{5} 51这样的数,类似的小数的二进制表示法只能表示那些能够被写成x×2y的数,其他的值只能够被近似地表示。

IEEE浮点表示

我们刚刚提到的定点表示法不能有效的表示非常大或者非常小的数字,比如说5×2100就是101后面跟100个零组成的位模式来表示,这时我们就可以给定x和y的值,来表示x×2y的数。
IEEE浮点标准使用 V = (-1)s × M × 2E 的形式来表示一个数:

  • 符号:s决定这个数是负数(s=1)还是正数(s=0),对于数值0的符号位解释为特殊情况处理。
  • 尾数:M是一个二进制小数,它的范围是1 ~ 2-ε,或者是0 ~ 1-ε。
  • 阶码:E的作用是对浮点数加权,这个权重是2的E次幂。

将浮点数的位表示划分为三个字段,分别对这些字进行编码:

  • 一个单独的符号位s直接编码符号s。
  • k位的阶码字段exp=ek-1···e1e0编码阶码E。
  • n位小数字段frac=fk-1···f1f0编码尾数M,但是编码出来的值也依赖于阶码字段的值是否为0。

单精度
在这里插入图片描述
双精度
在这里插入图片描述
给定了位表示,根据exp的值,被编码的值可以分成三种不同的情况。

1.规格化的值
这是最普遍的情况。当exp的位模式既不全为0,也不去全为1时,都输属于这类情况。在这种情况中,阶码字段被解释为偏置形式表示的有符号整数。也就是说,阶码的值是E=e-Bias,其中e是无符号整数,其位表示为ek-1···e1e0,而Bias是一个等于2k-1-1(单精度是127,双精度是1023)的偏置值。由此产生指数的取值范围,对于单精度是-126~+127,而双精度是-1022 ~ +1023。
对于小数字段frac的解释为描述小数值f,其中0 ≤ f < 1,其中二进制表示为0.fn-1···f1f0,也就是二进制小数点在最高有效位的左边。尾数定义为M=1+f 。有时,这种方式也叫做隐含的以1开头的表示

2.非规格化的值
但阶码域为全0时,所表示的数就是非规格化形式。在这种情况下,阶码值是E=1-Bias,而尾数的值是M=f,也就是小数字段的值,不包含隐含的开头的1
非规格化数有两个用途。首先,它们提供了一种表示数值0的方法,因为使用规格化数,我们必须总是使M≥1,因此我们就不能表示0。+0.0的浮点数表示的位模式为全0:符号位是0,阶码字段全为0(表示是一个非规格化的值),而小数域也全为0,这就得到M=f=0。
非规格化数的另外一个功能是表示那些非常接近于0.0的数。它们提供了一种属性,称为逐渐溢出,可能的数值分布均匀地接近于0.0。

3.特殊值
最后一类数值是当值阶码全为1的时候出现的。当小数域全为0时,得到的值表示无穷,当s=0时是+∞,当s=1时是-∞。当我们把两个非常大的数相乘,或者除以0时,无穷能够表示溢出的结果。当小数域为非零时,结果值被称为NaN,一些运算的结果不能是实数或无穷,就会返回这样的NaN值。

0.1+0.2!=0.3?
知道了浮点数在计算机中如何表示之后我们再来看下刚开头我们所说的那个奇怪的现象,为什么0.1+0.2!=0.3。
首先我们先要把十进制的0.1转换成IEEE标准的浮点表示。先把0.1先转换成二进制表示的小数,对于整数部分进行除2取余数,小数部分乘2取整。那么对于0.1来说整数部分仍是0,而小数部分则是:
0.1 × 2 = 0.2 0
0.2 × 2 = 0.4 0
0.4 × 2 = 0.8 0
0.8 × 2 = 1.6 1
0.6 × 2 = 1.2 1
0.2 × 2 = 0.4 0
0.4 × 2 = 0.8 0
0.8 × 2 = 1.6 1
0.6 × 2 = 1.2 1
0.2 × 2 = 0.4 0
0.4 × 2 = 0.8 0
0.8 × 2 = 1.6 1
0.6 × 2 = 1.2 1
0.2 × 2 = 0.4 0
······
所以0.1的小数部分转换成二进制就为0.000110011001100110011001100······
现在我们再来把它转换成x×2y 这样的形式,那么就变成了1.10011001100110011001100110011001100······×2-4,因为我们知道单精度尾数为23位。但是这里的0.1的小数部分编码是无限循环的,这就意味着我们是无法使用二进制的方式来精确表示0.1,所以我们就只能进行舍入,从这里开始就产生了误差,所以这就导致了0.1+0.2!=0.3的这样的问题的出现。既然没有那么多位来表示,我们就只能把多余的部分舍去,那么又应该采用什么样的舍入方式呢?

舍入
因为表示方法限制了浮点数的范围和精度,浮点运算只能近似地表示实数运算。因此,对于值x,我们一般想用一种系统的方法,能够找到“最接近的”匹配值x′,它可以用期望的浮点形式表示出来。这就是舍入运算的任务。一个关键问题是在两个可能值的中间确定舍入方向。例如,如果我有1.5元,想把它舍入到最接近的数量,应该是1元还是2元呢?一种可选择的方法是维持实际数字的上界和下界。例如,我们可以确定可表示的值x-和x+,使得的值x位于它们之间:x- ≤ x ≤ x+。IEEE浮点格式定义了四种不同的舍入方式。默认的方式是找到最接近的匹配值,而其他三种可用于计算上界和下界。

方式1.401.601.502.50-1.50
向偶数舍入1222-2
向零舍入1112-1
向下舍入1112-2
向上舍入2223-1

这里举例说明了应用四种舍入方式,将一个金额数舍入到最接近的整数。向偶舍入,也称为向最接近的值舍入,是默认的方式,试图找到一个最接近的匹配值。因此,他将1.4舍入成1,而将1.6舍入成2,因为它们是最接近的整数值。唯一的设计决策是确定两个可能结果中间数值的舍入效果。向偶数舍入采取的方法是:将数字向上或者向下舍入,使得结果的最低有效数字偶数。因此,这种方法将1.5和2.5都舍入成2。

其他三种方式产生实际值的确界。这些方法在一些数学应用中很是有用的。向零舍入方式把正数向下舍入,把负数向上舍入,得到值x^ ,使得|x^| ≤ |x|。向下舍入方式把正数和负数都向下舍入,得到值x-,使得|x-| ≤ |x|。向上舍入方式把正数和负数都向上舍入,得到值x+,使得|x+| ≥ |x|。

向偶数舍入最初看上去好像是个相当随意的目标——有什么理由偏向取偶数呢?为什么不始终把位于两个可表示的值中间的值都向上舍入呢?使用这种方法的一个问题就是很容易假想到这样的情景:这种方法舍入一组数值,会在计算这些值的平均数中引入统计偏差。我们采用这种方法舍入得到一组数的平均值将比这些数本身的平均值略高一些。相反,如果我们总是把两个可表示值中间的数字向下舍入,那么舍入后的一组数的平均值将比这些数本身的平均值略低一些。向偶舍入在大多数现实情况中避免了这种统计偏差。在50%的时间里,它将向上舍入,而在50%的时间里,它将向下舍入。

在我们不想舍入到整数时,我们也可以使用向偶数舍入。我们只是简单地考虑最低有效数字是奇数还是偶数。例如,我们假设我们想将10进制数舍入到最接近的百分位。不管用哪种舍入方式,我们都将把1.2349999舍入到1.23,而将1.2350001舍入到1.24,因为它们不是在1.23和1.24的正中间。另一方面我们将把两个数1.2350000和1.2450000都舍入到1.24,因为4是偶数。

相似的,向偶舍入法能够运用于二进制小数。我们将最低有效位的值0认为是偶数,值1认为是奇数。一般来说,只有对形如XX···X.YY···Y100的二进制位模式的数,这种舍入方式才有效,其中X和Y表示任意位值,最右边的Y是要舍入的位置。只有这种位模式表示两个可能结果的正中间的值。例如,考虑舍入值到最近的四分之一的问题(也就是二进制小数点右边两位)。我们将10.000112(2 3 32 \frac{3}{32} 323)向下舍入到10.002(2),10.001102(2 3 16 \frac{3}{16} 163)向上舍入到10.012(2 1 4 \frac{1}{4} 41),因为这两个值不是两个可能值的正中间值。我们将10.111002(2 7 8 \frac{7}{8} 87)向上舍入成11.002(3),而10.101002(2 5 8 \frac{5}{8} 85)向下舍入成10.102(2 1 2 \frac{1}{2} 21),因为这些值是两个可能值的中间值,并且我们倾向于使最低有效位为0

浮点运算
在了解了舍入的方式之后我们就继续对0.1的二进制小数进行处理,因为单精度浮点数数尾数段为23位,所以我们将会在这里进行舍入1.10011001100110011001100110011001100······×2-4,显然这里是需要进位的。那么我们就知道这里对于0.1来说尾数段编码就为10011001100110011001101,而它权值为-4,那么阶码则为-4+127=123,转换成二进制就是01111011,所以现在我们就得到了0.1(10)的IEEE标准的二进制表示:

0 01111011 1001100 11001100 11001101(2)

同样的我们可以计算得到0.2(10)的二进制表示:

0 01111100 1001100 11001100 11001101(2)

现在我们就得到了0.1和0.2的IEEE标准的二进制表示,现在我们又面临了一个新的问题,又怎么使用这样的二进制表示来计算0.1+0.2的结果呢?

加法

前面已讲到,浮点数经常被写成如下的形式:V = (-1)s × M × 2E
假定有两个浮点数 X = Mx × 2Ex, Y = My × 2Ey
实现X+Y运算,要用如下几步完成:

  1. 对阶操作,即比较两个浮点数的阶码值的大小.求△E=Ex-Ey。当其不等于零时,首先应使两个数取相同的阶码值。其实现方法是,将原来阶码小的数的尾数右移|△E|位,其阶码值加上|△E|,即每右移一次尾数要使阶码加1。尾数右移时,尾数高位补0。
  2. 实现尾数的加运算,对两个完成对阶后的浮点数执行求和操作。
  3. 规格化处理
  4. 舍入操作。在执行对阶或右规操作时,会使尾数低位上的一位或多位的数值被移掉,使数值的精度受到影响。

乘法

假定有两个浮点数 X = Mx × 2Ex, Y = My × 2Ex
实现X×Y运算,得到 R = M × 2E,其中:

  1. M = Mx × My
  2. E = Ex + Ex

如果M ≥ 2,则右移,E + 1,然后同样进行规格化处理和舍入操作。

知道了浮点数的加法之后我们再来看0.1 + 0.2 如何计算:

0.1 = 1.1001100 11001100 11001101 × 2 -4
0.2 = 1.1001100 11001100 11001101 × 2 -3

所以将0.1进行对阶,变成:
0.11001100 11001100 11001101 × 2 -3

加上0.2
1.10011001100110011001101 × 2 -3

最终的到:
10.011001100110011001100111 × 2 -3

规格化处理一下:
1.0011001100110011001100111 × 2 -2

转换为IEEE标准的浮点数格式得到:

0 01111101 00110011001100110011010

转换成十进制为0.300000011920928955078125,可以看到最终结果并不是0.3,其实在很多地方就开始产生了一些误差,比如说在0.1和0.2转换IEEE标准的浮点数时,就因为表示方法不能准确表示的原因产生了误差。在0.1+0.2因为对阶操作的移位也对精度产生了影响,并且在对最终结果的舍入时也产生了误差。最终我们得到一个和我们预期的所不同的结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值