一文带你了解计算机进制

640 阅读12分钟

进制简介

进制也称为进位计数制,可以用有限的数字符号代表所有的数值。可使用的数字符号数目称为基数或者底数,基数为n,即可称为n进位制,简称n进制。

对于任何一种进制---X进制,表示在每一位数上运算时,都逢X进一位。十进制逢十进一,十六进制逢十六进一,二进制逢二进一,以此类推,x进制就是逢x进一

十进制(Decimal):平时我们最常用的是十进制,由0,1,2,3,4,5,6,7,8,9这10个阿拉伯数字组成。

二进制(Binary):在计算机中使用的是二进制,由0,1组成。

八进制(Octal):由于二进制的基数R比较小,书写和阅读不方便,所以在小型计算机中引入了八进制,由0,1,2,3,4,5,6,7组成,每个数码正好对应三位二进制。

十六进制(Hexadecimal):由于二进制在使用中位数太长,不容易记忆和书写,所以又提出了十六进制,由0-9加上字母A-F组成。

同一个数值可以用不同的进制表示,如66:

十进制:66

二进制:1000010

八进制:0102

十六进制:42

进制相关概念

  1. 数码:将数值中的每一位数字称为数码
  2. 位数:数码在这个数值中的位置,从右向左开始递增。
  3. 基数:每一位数码可以用多少数字来表示,例如:二进制中每一位的数码只能用0或1来表示,所以二进制的基数为2。基数其实就是所谓的进制,十进制基数为十,二进制基数为二。
  4. 位权:处在某一位置上的数码所表示数值的大小,称为该位的位权。公式为:基数的位数次幂。

如:十进制的123:

  1. 123中的数字:1,2,3则称为数码。
  2. 123中3的位数为0,2的位数为1,1的位数为2,从右向左从0开始递增。
  3. 123是用十进制表示的,所以123的基数为10。
  4. 123中3它表示的位权为10^0=1,2的位权为10^1=10,1的位权为1*10^2=100。

进制转换

进行进制转换前,我们先了解一下各个进制间的关系:

十进制二进制八进制十六进制
0000
1111
21022
31133
410044
510155
611066
711177
81000108
91001119
10101012A
11101113B
12110014C
13110115D
14111016E
15111117F
16100002010

十进制转二进制

计算方式:使用除基取余法,也就是使用十进制的整数除以二进制的基数2,余数为权位上的数,得到的商继续除以2,直到商为0为止。读数时,从最后一位读起。

例如:

十进制数:67转二进制后,二进制为:1000011

计算过程如下:

第N次运算运算余数
第1次67/2331
第2次33/2161
第3次16/280
第4次8/240
第5次4/220
第6次2/210
第7次1/201

计算完成后,从最后一位读起,结果为:1000011

以上是十进制整数部分转二进制的计算方式,我们再来看一下小数部分是如何转换的。

小数部分的计算方式:使用了乘基取整法,使用小数乘以二进制的基数2,整数部分为权位上的数,小数部分继续乘以2,知道小数部分为0为止。读数时,从第一位开始读起。

例如:

0.2转二进制后,二进制为:0.001100110011001100110011001100110011001100110011001101...

计算过程如下:

第N次运算运算整数部分
第1次0.2*20.40
第2次0.4*20.80
第3次0.8*21.61
第4次0.6*21.21
第5次0.2*20.40
第6次0.4*20.80

计算完成后,读数从第一位读起。可以看到在第5次计算的时候与第1次是一样的,形成了一个循环,所以0.2的二进制是一个无限循环的数值:0. 0011 0011 0011...

二进制转十进制

计算方法:把二进制按权展开,相加既得十进制。

例如

十进制数:1000011转十进制后,十进制为:67

计算过程如下:

二进制:1000011
位数6543210
公式1*2^60*2^50*2^40*2^30*2^21*2^11*2^0
结果64000021

最后将结果相加:64 + 0 + 0 + 0 + 0 + 2 + 1 = 67

十六进制转二进制

计算方式:和十进制的计算方式一样,使用除基取余法,从最后一位读起。

例如:

67的十六进制为43,将43转为二进制为0100 0011

需要将十六进制拆分为以4个二进制为单位,也就是4,3。然后对4和3分别使用除基取余法,计算的结构如果不足4位,则在最左边补零

4的计算过程如下:

第N次运算运算余数
第1次4/220
第2次2/210
第3次1/201

计算完成,从最后一位读起,计算结果为:100,不足4位在最左边补零,最终结果为:0100

3的计算过程如下:

第N次运算运算余数
第1次3/211
第2次1/201

计算完成,从最后一位读起,计算结果为:11,不足4位在最左边补零,最终结果为:0011

然后将两次运算结果合并:01000011

二进制转十六进制

计算方式:将二进制从右向左以4位一组的形式按权展开,不足4位时在最左边补零,最后相加得到一位十六进制的数。

例如:

二进制为1000011转为十六进制为43

首先将二进制1000011从右向左以4位一组分开,不足4位补零,结果为:

  1. 0100
  2. 0011

0100计算过程如下:

二进制:0100
位数3210
公式0*2^31*2^20*2^10*2^0
结果0400

最后将结果相加:0 + 4 + 0 + 0 = 4,0100 = 4

0011计算过程如下:

二进制:0011
位数3210
公式0*2^30*2^21*2^11*2^0
结果0021

最后将结果相加:0 + 0 + 2 + 1 = 3,0011 = 3

最终结果为:43

十六进制转十进制

计算方式:将十六进制按权展开,相加既得十进制数。

例如:

十六进制43转十进制,十进制数为:67

计算过程如下:

十六进制:43
位数32
公式4*16^13*16^0
结果643

计算完成,将结果相加:64 + 3 = 67

十进制转十六进制

计算方式:使用除基取余法,十进制数除以十六进制的基数16,余数为权位上的树,使用商继续除以16,直到商为0为止,读树时从最后一位起读。

例如:

十进制67转十六进制,十六进制为43

计算过程如下:

第N次运算运算余数
第1次67/1643
第2次4/1604

计算完成,读数从最后一位读起,结果为:43

javascript中的进制转换

toString

平常使用toString方法,一般是将非字符串类型转换为字符串类型,但是它还有一个功能则是用于将number类型的数值进行进制转换。

语法:

numObj.toString([radix])

(67).toString(2) // '1000010'
(67).toString(8) // '103'
(67).toString(16) // '43'

(67).toString(1) // RangeError: toString() radix argument must be between 2 and 36

radix参数指定数字到字符串的转换的基数,基数需要大于等2,小于等于36。如果未指定radix参数,则默认基数为10。

parseInt

parseInt的作用是解析一个字符串并返回指定基数十进制整数

语法:

parseInt(string, radix);

radix参数指定数字到字符串的转换的基数,基数需要大于等2,小于等于36。注意:如果未指定radix参数,10不是默认值。ECMAScript 5 规范不再允许parseInt函数的实现环境把以0字符开始的字符串作为八进制数值,也就是说现在只有两种情况:如果radix是undefined0或未指定的,参数会被假定以10为基数来解析,如果数值以字符对0x或0X开头,会假定以16为基数来解析。

parseInt('1000011', undefined) // 1000011 按十进制解析
parseInt('1000011', 2) // 67 按二进制解析
parseInt('08') // 8 按十进制解析
parseInt('0x43') // 67 按十六进制解析

实战题

['1', '2', '3'].map(parseInt)

有些同学看到这题,脑子里一下就有了答案:[1, 2, 3];

恭喜你,回答错误。

首先我们先来分析一下代码:

  1. 有一个元素为stringArray
  2. 使用了map方法
  3. map方法中传入的回调函数是parseInt方法

我们先搞清楚map方法,它需要传入一个回调函数,这个函数中有三个参数:

  1. currentValue:数组中正在处理的当前元素。
  2. index:数组中正在处理的当前元素的索引。
  3. array:map方法调用的数组,也就是['1', '2', '3']

然后是parseInt方法,通过上面我们知道parseInt需要传两个参数:

  1. string:需要被转为number类型的字符串

  2. radix:基数,范围在2-36之间(包括2和36),为0,undefined或不传则默认为10,超出该范围,parseInt无法解析则返回NaN

回过头再来看代码:

['1', '2', '3'].map(parseInt)

转为

['1', '2', '3'].map((currentValue, index, array) => {
    // 第一次循环:parseInt('1', 0);
    // 第二次循环:parseInt('2', 1);
    // 第二次循环:parseInt('3', 2);
    return parseInt(currentValue, index);
});
  1. 第一次循环中调用parseInt,值传入的'1',radix传入的0parseInt的基数则默认使用10(十进制)来解析,结果为1
  2. 第二次循环中调用parseInt,值传入的'2',radix传入的1,上面我们说过,radix的值需要大于等于2,小于等于36,超出则无法解析,所以记过为:NaN
  3. 第三次循环中调用parseInt,值传入的'3',radix传入的2,radix也是正确的,但是二进制中是由0和1组成的,3不是一个正确的二进制,解析会出错,所以结果为:NaN

最终结果为:[1, NaN, NaN]

['1.1', '2', '0.3'].map(parseInt)

这道题的结果会是多少呢?

公布答案:[1, NaN, 0]

我们来看下代码分析:

['1.1', '2', '0.3'].map(parseInt)

转为

['1.1', '2', '0.3'].map((currentValue, index, array) => {
    // 第一次循环:parseInt('1.1', 0);
    // 第二次循环:parseInt('2', 1);
    // 第二次循环:parseInt('0.3', 2);
    return parseInt(currentValue, index);
});

注意:这道题比上面那道题多考一个知识点:parseInt只会解析数值,遇到非数值类型则会停止,并返回之前解析的数值。

  1. 第一次循环中调用parseInt,值传入的'1.1',radix传入的0parseInt的基数则默认使用10(十进制)来解析,解析完1后,继续向下解析遇到小数点符号,遇到非数值类型停止解析,返回之前解析的数值,结果为1
  2. 第二次循环中调用parseInt,值传入的'2',radix传入的1,radix的值需要大于等于2,小于等于36,超出则无法解析,所以记过为:NaN
  3. 第三次循环中调用parseInt,值传入的'0.3',radix传入的2,以二进制来解析,遇到0,解析成功,继续向下解析遇到小数点符号,遇到非数值类型停止解析,返回之前解析的数值,结果为0

最终结果为:[1, NaN, 0]

总结

  1. 除基取余法:进制数x转n进制,使用x除以n进制的基数n,余数为权位上的数值,将商继续除以n,直到商为0为止。读数时,从最后一位读起(倒序)。
  2. 乘积取整法:进制数x转n进制,使用x乘以n进制的基数n,获得的积的整数部分为权位上的数值,将小数部分继续乘以n,直到小数部分为0为止。读数时,从第一个读起(正序)。
  3. 十进制的数转任何进制数,整数部分都是使用除基取余法,小数部分使用乘积取整法
  4. 任何进制数十进制,都使用按权展开相加
  5. 二进制转为其它进制(不包括十进制),需要先根据进制从右向左进行分组,不足时补零,如:转4进制,需要以2位二进制为一组,转8进制需要以3位二进制为一组,转16进制需要以4位二进制为一组。然后按权展开相加。
  6. 在javascript中也有内置的进制转换方法:
  • number.toString(radix):将number类型转换为其它进制数(字符串类型),radix不传,为0或为undeined时,默认为10(十进制),范围在2-36之间(包括2和36),超出则会报错(RangeError)。
  • parseInt(string, radix):根据基数来解析字符串,并返回一个十进制的整数。radix不传,为0或为undeined时,默认为10(十进制),范围在2-36之间(包括2和36),当字符串以0X或者0x开头则默认使用16(十六进制)来解析。