You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
function getDecimal(binary) {
let number = 0;
let small = binary.slice(2);
for (let i = 0; i < small.length; i++) {
const num = parseInt(small[i]) * Math.pow(2, 0 - i - 1);
number += num;
}
return number;
}
console.log(getDecimal('0.0001')); // 0.0625
表面工作
在日常的工作和学习中,经常会探测自己的底线,计算机基础好与不好,完全能够决定一个人的代码水平和bug出现率。相信大家对这些知识都学过,只是长时间不用就忘记了,今天带大家来回顾一下。
本着通俗易懂的原则,今天把这个题目讲明白。
我们来聊聊这个非常常规的问题,为什么
0.1 + 0.2 !== 0.3
.在正式介绍这个问题之前,需要了解下面几个前置知识。
差不多这几个就够理解这个常规的
0.1 + 0.2 !== 0.3
问题了。第一个前置知识,二进制
我们知道在日常中,有很多种数据的展现,包括我们日常生活中常规使用的10进制、css中表示颜色的16进制、计算机中进行运算的二进制。
二进制的表现形式
在计算机中的计算都是以二进制的形式进行计算的,也就是全都是0或1来表示数字的,我们拿10进制进行举例,如:
二进制的计算方式
对于二进制的计算方式,我们分为两种情况来说,一种是整数的计算,一种为小数的计算。
整数部分的二进制计算
我们先说明10进制如何转化为二进制。10进制转化为二进制的方式称为“除 2 取余法”,即把一个10进制数,一直除以2取其余数位。举两个例子
整数的二进制转换是从下往上读的,所以30的二进制表示即为
11110
.整数的二进制转换是从下往上读的,所以100的二进制表示即为
1100100
.我还专门写了一个函数来转换这个二进制。
接下来,我们再看看怎么把二进制转换成10进制。通俗点讲就是从右到左用二进制的每个数去乘以2的相应次方并递增。举个例子,拿上面的100举例子吧。100的二进制表示为
1100100
,我们需要做的是:简单明了,不用多说,看下实现代码:
小数部分的二进制计算
小数部分的二进制计算与整数部分的二进制计算不同,十进制的小数转化为二进制的小数的计算方式称为“乘二取整法”,即把一个十进制的小数乘以2然后取其整数部分,直到其小数部分为0为止。看个例子:
且小数部分的读取方向也不一样。小数的二进制转换是从上往下读的,所以0.0625的二进制表示即为
0.0001
,这个是正好能够除尽的情况,很多情况下是除不尽的,例如题目中的0.1和0.2。写个函数转换下:再尝试把二进制的小数转换为十进制的小数,因为上面是乘,所以在这边就是除法了,二进制的除法也是可以表示为负指数幂的乘法的,比如
1/2 = 2^-1
;我们来看下0.0001怎么转换为0.0625:用函数来实现下这个形式吧。
二进制转换这一部分我们就先了解到这里,对于
0.1 + 0.2 !== 0.3
这个问题,上面的二进制部分,基本是足够了。当然代码部分仅作参考,边界等问题没有做处理...做个题巩固一下:
18.625 的二进制表示是什么 ??? => 点击查看详情
第二个前置知识,计算机码
我们知道,计算机中是使用二进制来进行计算的,讲到计算机码,就不得不提 IEEE标准,而涉及到小数部分的运算就不得不提到 IEEE二进位浮点数算术标准的标准编号(IEEE 754)。其标准的二进制表示为
为什么1和小数位要省略呢?因为所有的第一位都为1,省略后可以再末尾再多一位,增加精确度。如果第一位为0的话,那没有任何意义。
一般来说,现在的计算机都支持两种精度的计算浮点格式。一种为单精度(float),一种为双精度(double)。
以JavaScript为例,js中使用的是双精度格式来进行计算的,其浮点数是64位。
原码
什么是原码,原码是最简单的,就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值。我们用11位表示如下:
因为第一位是符号位,所以其取值区间为[111 1111 1111, 011 1111 1111] = [-1023, 1023];
反码
什么是反码,反码是在原码的基础上进行反转。正数的反是其本身;负数的反码是符号位不变,其余位取反。
补码
什么是补码,补码是在反码的基础上补位。正数的补码是其本身,负数的补码是在其反码的基础上,再加1.
+1 = [000 0000 0001]原 = [000 0000 0001]反 = [000 0000 0001]补
-1 = [100 0000 0001]原 = [111 1111 1110]反 = [111 1111 1111]补
为什么会有补码这玩意呢?
首先在计算机中是没有减法的,都是加法,比如 1 - 1 在计算机中是 1 + (-1).
如果使用原码进行减法运算:
===>>> 结论:不对
为解决这个不对的问题于是就有了反码去做减法:
发现值是正确的,只是符号位不对;虽然+0和-0在理解上是一样的,但是0带符号是没有意义的,况且会出现 [000 0000 0000]原 和 [100 0000 0000]原 两种编码方式。
===>>> 结论:不大行
为解决上面这个符号引起的问题,就出现了补码去做减法:
这样得到的结果就是完美的了,0用 [000 0000 0000] 表示,不会出现上面 [100 0000 0000]。
===>>> 结论:完美
移码
移码,是由补码的符号位取反得到的,一般用做浮点数的阶码,引入的目的是为了保证浮点数的机器零为全0。这个不分正负。
细心一点可以发现规律:
为什么 0.1 + 0.2 !== 0.3 ?
回到我们的题目,我们来看下为什么
0.1 + 0.2 !== 0.3
.来看下0.1和0.2的二进制表示。可以得知0.1和0.2都是一个0011无限循环的二进制小数。
我们由上面知道,JavaScript中的浮点数是64位来进行表示的,那么0.1和0.2是在计算机中又是如何表示的呢?
根据IEEE 754标准可以得知:
同理可知0.2的表示:
两者相加,阶码不相同,我们需要进行对阶操作。
对阶
对阶就会存在尾数移动的情况。
计算机采取的是后者,小看大的办法。这也就是今天这个问题产生的原因,丢失了精度。
那么接下来,我们就看看上面的这个移动。
如上就是二进制中0.1和0.2的对阶后的结果,我们对这个数字进行运算比较麻烦,所以我们直接拿0.1和0.2的真值进行计算吧。
真值计算
这特么不对啊!!!
我们在浏览器运行的时候得到的值是:
产生上面问题的原因,是在于计算机计算的时候,还会存在舍入的处理
如上面来看,真值计算后的值舍弃的值是1100,在计算机中还会存在舍0入1,即如下:
到此,我们就把这部分聊明白了,如有不对之处,欢迎指出。感谢阅读。
公众号
[
德莱问前端
] ,欢迎关注,文章首发在公众号上面。除每日进行社区精选文章收集外,还会不定时分享技术文章干货。
希望可以一起学习,共同进步。
The text was updated successfully, but these errors were encountered: