二进制小数如何转换为十进制
二进制转换十进制的方法是:
- 从二进制数的最低位开始,每一位乘以对应的2的幂数,然后将最终的结果小数部分与整数部分分别相加
- 对应的2的幂,以个位为0,向高位依次增1,向地位依次减1;
举个例子: 以二进制小数1100.0011
为例:
二进制小数位 | 1 | 1 | 0 | 0 | . | 0 | 0 | 1 | 1 |
---|---|---|---|---|---|---|---|---|---|
对应2的幂 | 3 | 2 | 1 | 0 | . | -1 | -2 | -3 | -4 |
乘幂计算 | 1 * 23 | 1 * 22 | 0 * 21 | 0 * 20 | . | 0 * 2-1 | 0 * 2-2 | 1 * 2-3 | 1 * 2-4 |
结果 | 8 | 4 | 0 | 0 | . | 0 | 0 | 0.125 | 0.0625 |
当幂为负数时,乘幂计算实际上就是除以对应幂数的倒数,即1 * 2-3 = 1 / 23 = 1/8 = 0.125;
所以最终的结果就是 8 + 4 . 0.125 + 0.0625 = 12.1875;
我们在浏览器控制台验证一下,看是否正确:
二进制转十进制javascript
实现:
// 这里参数n要直接传入二进制小数的字符串形式,
//如果直接传入一个二进制小数,无论是利用'' +n 还是利用String(n)去转字符串,都会将原值截断,只保留19位,导致最后计算错误
function BinToDec(n) {
// let arr = ('' + n).split('.'); 这样会导致计算错误。
let arr = n.split('.');
let ZS = arr[0].split('');
let XS = arr[1] ? arr[1].split(''): [];
let zsSum = 0;
let xsSum = 0;
let i = 0;
let j = 1;
while (ZS.length) {
let num = parseInt(ZS.pop());
zsSum += Math.pow(2, i) * num;
i++;
}
if(XS.length) {
while(XS.length) {
let num = parseInt(XS.shift());
xsSum += Math.pow(2, -j) * num;
j++;
}
}
return zsSum + xsSum;
}
十进制小数如何转为二进制小数
十进制小数转换为二进制是整数部分与小数部分分别计算,然后再相加的。
整数十进制转二进制 —— 除2取余法【短除法】
不断除以2,直到商为0,每一步得到的余数依次由低到高填充到二进制的位置里。
因为任何一个十进制除以2的余数要么是1,要么是0,所以最后这些余数就构成了最后的二进制数。
比如将174
这个数字转换为二进制的过程:
除以2 | 商 | 余数 | 二进制第几位 |
---|---|---|---|
174/2 | 87 | 0 | 0 |
87/2 | 43 | 1 | 1 |
43/2 | 21 | 1 | 2 |
21/2 | 10 | 1 | 3 |
10/2 | 5 | 0 | 4 |
5/2 | 2 | 1 | 5 |
2/2 | 1 | 0 | 6 |
1/2 | 0 | 1 | 7 |
所以,得到十进制174转换为二进制为: 10101110
(注意由高位到低位书写,和表格中计算得到的顺序相反)
用javascript
递归实现:
:
/**
* [integerToBin 整数转二进制]
*
* @param {[type]} n [n description]
*
* @return {[]} [return description]
*/
function integerToBin(n) {
// 如果商大于0,继续递归调用,否则返回空字符串用于与前面的结果连接
if (n > 0) {
// 获得数字除2后的商
let quotient = parseInt( n / 2);
let remainder = n % 2;
return integerToBin(quotient) + '' + remainder;
}
return '';
}
小数十进制转二进制——乘2取整法
-
将小数的小数部分取出,乘以2,将得到的结果中的整数部分作为二进制小数的项
-
得到结果中的小数部分重复第一步的步骤
-
直到某一步乘以2的值的小数部分为0,或者小数部分形成循环小数则停止
比如
0.1875
这个十进制小数,计算转换为二进制的过程:乘以2 积 整数部分 小数部分 0.1875 * 2 0.375 0 0.375 0.375 * 2 0.75 0 0.75 0.75 * 2 1.5 1 0.5 0.5 * 2 1.0 1 0 得到结果为:
0011
(与整数转换不同,这里是顺序)用
javascript
的递归实现:
/**
* [fracToBin 小数转二进制]
*
* @param {[type]} n [n 需要转换为二进制的小数]
* @param {[type]} bits [bits 准换后的二进制小数的位数]
* @param {undefined[]} bin [arr 转换后的二进制小数,默认为空]
*
* @return {[]} [return description]
*/
function fracToBin(n, bits = 49, bin = '') {
// 如果二进制位小于给定的位数
if (bin.length < bits) {
// 1. 将需要转换的小数分为整数部分与小数部分,获得小数部分
let xs = ('' + n).split('.')[1];
// 2. 小数部分乘以2
// 2.1小数部分字符串转数字
let xsNumber = parseFloat('0.' + xs);
// 2.2 小数部分乘以2
let a = xsNumber * 2;
// 3. 取出数字的整数部分拼接到二进制小数的结果中
let ta = ('' + a).split('.');
bin += ta[0];
let na = parseFloat('0.' + ta[1]);
// 如果小数部分等于0,则返回
if (parseFloat(na) == 0) {
return bin;
}
// 否则递归
return fracToBin(na, bits, bin);
}
return bin;
}
用代码表示十进制转换二进制的整体计算过程:
/**
* [DecToBin 十进制转二进制]
*
* @param {[type]} n [n description]
*
* @return {[type]} [return description]
*/
function DecToBin(n) {
let inter, frac;
let arr = ('' + n).split('.');
inter = arr[0] === '0' ? arr[0] : integerToBin(arr[0]);
frac = arr[1] ? '.' + fracToBin(parseFloat('0.' + arr[1])) : '';
return parseFloat([inter, frac].join(''));
}
DecToBin(2.3555);
当然,以上代码仅用于展示十进制小数转换为二进制小数的过程,实际开发中,我们可以直接像下面这样转换:
let a = 2.3555;
console.log(a.toString(2));
二进制小数的存储
浮点数
我们将二进制小数算出来还不算完,还要明白计算机中如何存储二进制小数的。
小数,在计算机语言里,准确应该叫做浮点数。
而浮点数根据精确度不同分为很多种,最常用的有两种:
- 单精度浮点数,采用32位二进制位存储
- 双精度浮点数,采用64位二进制位存储
浮点数精度
所谓精度,就是二进制数能够表达的数的精确度,在计算机中,二进制的存储存在着浮点数精度丢失的风险。
我们来看下小数点后四位能够用二进制所表示的十进制数:
二进制数 | 对应的十进制数 |
---|---|
0.0000 | 0 |
0.0001 | 0.0625 |
0.0010 | 0.125 |
0.0011 | 0.1875 |
0.0100 | 0.25 |
0.0101 | 0.3125 |
0.0110 | 0.375 |
0.0111 | 0.4375 |
0.1000 | 0.5 |
0.1001 | 0.5625 |
0.1010 | 0.625 |
0.1011 | 0.6875 |
0.1100 | 0.75 |
0.1101 | 0.8125 |
0.1110 | 0.875 |
0.1111 | 0.9375 |
这里,左面这一列的二进制数是连续的,它已经穷尽了四位二进制所能表达的所有二进制数
但右边十进制这一列却不是连贯的。
想象一下,我们如何使用四位二进制来展示 0.0715这个数字?
答案是,我们无法用四位二进制来表示,甚至无法用有限的二进制位来表示,按照我们上面介绍过的十进制小数转换为二进制小数的方法,它将得到一个无限小数,然而计算机存储是有上限的,不可能给它无数个位来存储,所以就会将超出最高存储位数上限的部分给截掉,这就是精度丢失的原因。
浮点数存储
根据IEEE 754规范,计算机对浮点数的存储总共分为三部分:
符号位 + 指数位 + 尾数位
每部分的位数根据精度不同而不同,比如
- 32位单精度浮点数的存储格式: 1位符号位 + 8 位指数位 + 23 位尾数位
- 64位双精度浮点数的存储格式: 1位符号位 + 11位指数位 + 52位尾数位
上述三部分我们分别解释下:
-
符号位,代表数字的正负,为【1】时表示【负数】,为【0】时表示【正数或者0】
-
尾数位
我们都知道,一个十进制数可以使用多种方式表达,比如
3.14
这个十进制数,他可以表达为以下几种形式:- 314 * 10-2
- 0.314 * 101
- 0.00314 * 103
为了方便计算机处理,科学家们就规定了一个对于十进制数统一的表示规则:小数点前面是0,小数点后面第1位不能是0.
所以所有十进制数有了相同的表达形式:
- 3.14 => 0.314 * 101
- 175 => 0.175 * 103
- 0.003 => 0.3 * 10-2
同样地,二进制小数也可以有多种表达形式,比如
10.10
这个二进制数,可以表达为:10.10
* 201.010
* 21101.0
* 2-1
为了计算机方便处理,计算机科学家规定了对于一个二进制小数的表示规则: 将小数点前面的值固定为1,并且确保小数点后面的长度为规定的精度的尾数位数。
所以所有的二进制小数就有了相同的表达形式(以32位精度为例,其尾数尾数位23位):
- 10.10 =>
1.01000000000000000000000
* 21 - 0.0010 =>
1.00000000000000000000000
* 2-3 - 100.1 =>
1.00100000000000000000000
* 22
而且既然规定了所有数字的整数各位都是1,那么为了节省存储空间,这个1就可以省略了,最后仅保留小数部分,就是这个二进制小数的尾数,以上面三个为例:
- 10.10的尾数: 01000000000000000000000;
- 0.0010的尾数: 00000000000000000000000;
- 100.1 的尾数: 00100000000000000000000;
-
指数位
-
指数位采用EXCESS系统表现
-
EXCESS 系统表现是指,通过将指数部分表示范围的中间值设为 0,使得负数不 需要用符号来表示
-
拿扑克牌举例,比如我们有从A到K十三张扑克牌,现在我们以中间的 7 为 0,则根据EXCESS系统,形成了以下对应关系:
牌面值 EXCESS系统值 A -6 2 -5 3 -4 4 -3 5 -2 6 -1 7 0 8 1 9 2 10 3 J 4 Q 5 K 6 -
同样地,当精度为32位时,指数位为8位,它能表示的最大二进制数为
11111111
,即255, -
我们取它中间的数,即使用
11111111
除以2,得到二进制数01111111
(二进制中,一个数除以2实际上就是右移一位,左面补0),01111111
的十进制数对应的是127, -
根据EXCESS系统要求,我们将中间值127表示为0, 则最终会形成类似下面的表示
二进制值 十进制值 EXCESS值 …… …… …… 1111100 124 -3 1111101 125 -2 1111110 126 -1 01111111 127 0 10000000 128 1 10000001 129 2 10000010 130 3 …… …… …… -
所以上例中的三个数字的对应指数位分别是:(使用小数通用表示规则写出后,指数是2的n次方,这里的n代表EXCESS值,而指数位存储的则是对应的二进制值)
- 10.10的指数位:10000000
- 0.0010的指数位:1111100
- 100.1的指数位: 10000001
所以,上面三个小数
10.10
,0.0010
,100.1
,最终的二进制存储为(按照32位精度):二进制小数 统一表达 正负 指数 符号位 指数位 尾数位 10.10 1.01000000000000000000000
* 21正 1 0 10000000 01000000000000000000000 0.0010 1.00000000000000000000000
* 2-3正 -3 0 1111100 00000000000000000000000 100.1 1.00100000000000000000000
* 22正 2 0 10000001 00100000000000000000000 最终存储的二进制值就是 符号位+指数位+尾数位。
上表是32位精度下的存储,64位精度时的尾数为52位,指数中间值是01111111111 (即十进制的1023)对应EXCESS系统的0。其它以此类推。
-
二进制知识实战巩固
我们学习了以上知识后,为了检验我们是否掌握,就拿javascript
中最经典的0.1+0.2 !=== 0.3
为例,来分析一下其中的原因。
首先,我们要知道,javascript
存储的二进制数是64位精度的
1.转换
首先,我们分别将0.1
和0.2
转换为二进制小数,可以利用我们上面学过的转换方法,得到的结果是(保留了60位小数):
十进制小数 | 二进制小数 |
---|---|
0.1 | 0.000110011001100110011001100110011001100110011001100110011001 |
0.2 | 0.001100110011001100110011001100110011001100110011001100110011 |
2.改写为统一表达式
我们将二进制小数改写为统一表达式,由于统一表达式要求小数点后的位数要和当前精度(64)位的尾数位数一致(52位),而我们的二进制小数都保留了60位,即使经过改写为统一表达式后左移了几位,但还是多于52位,所以多余部分的我们要截去。
二进制小数 | 统一表达式 |
---|---|
0.000110011001100110011001100110011001100110011001100110011001 | 1.1001100110011001100110011001100110011001100110011001 * 2-4 |
0.001100110011001100110011001100110011001100110011001100110011 | 1.1001100110011001100110011001100110011001100110011001 * 2-3 |
注意:截取的时候不是直接去掉,而是为最大限度保留精度,采取 【0舍1入】的规则
上面两个二进制小数的小数部分第53位都为1,所以舍去的时候要加1,
即上面两个表达式其实不是最终表达式,最终表达式要在尾数部分+1,得到:
-
0.1
=>1.1001100110011001100110011001100110011001100110011010
* 2-4 -
0.2
=>1.1001100110011001100110011001100110011001100110011010
* 2-3
3.获得最终存储值
我们要分别转换为统一表达式后的符号位、指数位、尾数位
这里,尾数可以直接从表达式里拿到,符号位都是0(正数),唯一剩余的就是获取指数位了
在64位精度下,指数位位11位,所以最大二进制值为1111 1111 111
,取中间数,即除以2,右移一位,左位补零,得到0111 1111 111
(十进制1023)
然后根据EXCESS系统规则列出它前后的对应值:
二进制值 | 十进制值 | EXCESS系统值(指数值) |
---|---|---|
011 1111 1011 | 1019 | -4 |
011 1111 1100 | 1020 | -3 |
011 1111 1101 | 1021 | -2 |
011 1111 1110 | 1022 | -1 |
0111 1111 111 | 1023 | 0 |
100 0000 0000 | 1024 | 1 |
100 0000 0001 | 1025 | 2 |
100 0000 0010 | 1026 | 3 |
100 0000 0011 | 1027 | …… |
那么0.1
和 0.2
的指数位分别是EXCESS系统-4
和-3
所对应的值:
0.1
指数位:011 1111 1011
0.2
指数位:011 1111 1100
由此,获得0.1
与0.2
的二进制最终存储值:
十进制值 | 二进制存储值 |
---|---|
0.1 | 0 01111111011 1001100110011001100110011001100110011001100110011010 |
0.2 | 0 01111111100 1001100110011001100110011001100110011001100110011010 |
计算和
现在,我们将上表中两个二进制存储值进行相加,逢二进一,得到二进制存储结果:
0 0111 1111 101 0011001100110011001100110011001100110011001100110100
那么我们如何将它还原回二进制进而还原回十进制小数呢?
逆向工程开始:
- 第1位0,代表正数
- 第2到12位,代表指数,
0111 1111 101
对照上面的EXCESS系统表,是十进制的1021,对应指数位-2
- 第13位到64位,
0011001100110011001100110011001100110011001100110100
代表尾数 - 还原为统一表达式:
1.0011001100110011001100110011001100110011001100110100
* 2-2 - 得到无指数形式:
0.010011001100110011001100110011001100110011001100110100
然后我们利用第一节的二进制小数转换十进制的方法,得到结果:
所以,在javascript
中,0.1 + 0.2 === 0.30000000000000004
会返回true
;
,是十进制的1021,对应指数位-2
- 第13位到64位,
0011001100110011001100110011001100110011001100110100
代表尾数 - 还原为统一表达式:
1.0011001100110011001100110011001100110011001100110100
* 2-2 - 得到无指数形式:
0.010011001100110011001100110011001100110011001100110100
然后我们利用第一节的二进制小数转换十进制的方法,得到结果:
[外链图片转存中…(img-7q6d05kg-1632739132361)]
所以,在javascript
中,0.1 + 0.2 === 0.30000000000000004
会返回true
;
[外链图片转存中…(img-Msu3JYWH-1632739132363)]