您的位置:首页 > Web前端 > JavaScript

js浮点运算精度问题和IEEE754

2017-09-07 17:17 381 查看
原文链接

当我们使用一段时间的JS之后会遇到下面这个问题

0.1 + 0.2 === 0.3 // false


我们可以在控制台里面看到0.1+0.2输出的并不是0.3而是0.30000000000000004。那么为什么出现这样的问题呢。

其实出现这样的问题在于js使用了IEEE754二进制浮点数算术标准,这个标准对于1/2, 1/4, 1/8等数字有很好的支持,其实这个标准也是现在大多数语言,CPU和浮点计算器选择的浮点数算术标准。

因为IEEE754是二进制的规则,所以可以看做用
n*2^m
这样的表示方式,这样的表达式在有限的存储空间下无法表示
n*10^m
类型数字,所以很简单的
0.1
无法被准确的表示。

但是类似Java等很多语言在底层会对浮点预算有处理,所以看上去好像没有遇到JS中的问题

有人认为, JavaScript 应该采用一种可以精确呈现数字的实现方式。 一直以 来出现过很多替代方案,只是都没能成为标准,以后大概也不会。这个问题 看似简单,实则不然,否则早就解决了。

为什么说在有限的存储空间下无法准确表示呢,那么让我们来了解一下IEEE754是如何来表示一个数字的吧。

关于IEEE754有单精度和双精度两种方式,两个方式的计算规则都是一样的,只是单精度使用32位来存储一个数字,而双精度使用64位来存储,只是用于存储的位数的大小。

关于IEEE754每位表示的意思,这里不详细说明,详细可以查看wiki IEEE 754

简单来说就是IEEE754对于数字的表达方式是

n = (-1)^s * 2^(e-127) * (1 + f)


(右边为第0位)

单精度 s : 第31位

e : 第30至23位

f : 第22指0位

单精度 s : 第63位

e : 第62至52位

f : 第51指0位

我们举一个例子来说明IEEE的表达方式

sef
00111 11101100 0000 0000 0000 0000 000
在这里s = 0,e转为10进制是126, f中左数第一位表示
1 / 2^1
,第二位表示
1 / 2^2
,依次类推,所以在这里
f = 1 / 2^1 + 1 / 2^2 = 0.75
;

所以
n = (-1)^0 * 2^(126-127) * (1+0.75) = 0.875
;

倒过来,如果我们给到的数字是23.56,那么首先我们先用二进制表示这个数字,为
10111.1000111101011100001
,然后我们将小数点移到前面只有一位数字,这里我们左移了4位

变成
1.01111000111101011100001
,然后除去第一位的
01111000111101011100001
填入22-0位,因为是正数,所以第31位为0,然后我们左移了4位,所以说明
(e-127) = 4
,所以
e=131
,转为二进制,所以第30至23位为
1000 0011
,从而得到了结果。

下面我们来看一下ieee754中那个无法准确表达的0.1。

首先将0.1转为二进制,10进制转二进制可以在网上查到,在如果不考虑存储的话,应该是0.000110001100011…,可以看到是00011的无限循环,其实如果在存储长度没有限制的情况下,通过简单的计算我们可以看到这个无限循环就是等于0.1。

000011 = 3 * 2 / (1 / 2^4), 等式为 3* 2 ( 1/2^4 + 1/2^8 … 1/2^4n)。

通过等比数列运算 s = 3 * 2 * (1/2^4) * (1- (1/2^4)^n) / (1 - 1 /2^4) = 3 * 2 * 1 / 15 = 0.1

那么我们有什么办法可以在js中安全的使用浮点运算呢,可能说没有有完美的解决办法

但是还是有一些简单的处理办法。

对于计算,我们可以使用
toFixed
来处理
toFixed
是用来强制保留小数点后面的位数,可以用于大多数精度要求不是非常高的计算中

(0.1 + 0.2).toFixed(2) // 0.30


最常见的方法是设置一个误差范围值, 通常称为“机器精度”(machine epsilon) , 对
JavaScript
的数字来说,这个值通常是
2^-52 (2.220446049250313e-16)


从 ES6 开始, 该值定义在
Number.EPSILON
中, 我们可以直接拿来用, 也可以为 ES6 之前 的版本写 polyfill:

if (!Number.EPSILON) {
Number.EPSILON = Math.pow(2,-52);
}


可以使用
Number.EPSILON
来比较两个数字是否相等(在指定的误差范围内):

function numbersCloseEnoughToEqual(n1,n2) {
return Math.abs( n1 - n2 ) < Number.EPSILON;
}

var a = 0.1 + 0.2;
var b = 0.3;

numbersCloseEnoughToEqual( a, b ); // true
numbersCloseEnoughToEqual( 0.0000001, 0.0000002 ); // false


原文链接
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: