一文了解JavaScript进制和进制转换
进制介绍
JavaScript中提供了四种表示进制的方法:二进制、八进制、十进制、十六进制。
不同的数值类型相应地有不同的数值字面量格式,主要是使用不同的前缀来区分:
- 二进制(Binary):取值数字 0 和 1 ;前缀 0b 或 0B。
- 八进制(Octal):取值数字 0-7 ;前缀 0o 或 0O。
- 十进制(Decimal):取值数字 0-9;不用前缀。
- 十六进制(Hexadecimal):取值数字 0-9 和 a-f ;前缀 0x 或 0X。
二进制数字的表现形式,是ES6中新增的功能,用于避免在以前的版本中使用parseInt函数转换二进制字符串到十进制数字时的不便。
例如,要表示二进制数1011,可以这样写:
let binaryNumber = 0b1011;
console.log(binaryNumber); // 11这样,就可以直接在代码中书写二进制数了,而不必使用以前常用的parseInt方法:
let binaryNumber = parseInt("1011", 2);
console.log(binaryNumber); // 11使用0b前缀更加简洁和直观。
对于八进制字面量,在ES6之前前缀使用0表示八进制。如果前缀0后面是相应的八进制数字(0~7),该数值被视为八进制;但如果前缀0后面跟随的数字中有 8或者9,则视为十进制。
需要注意的是,ES5中新增了严格模式,在严格模式下前缀为0的八进制字面量是无效的,并且还会导致Javascript引擎抛出语法错误。在严格模式下,我们需要使用前缀为0O的八进制字面量。
默认情况下,二进制、八进制、十六进制字面量数值,都会自动转为十进制进行运算。
0b110111 // 55
0o67 // 55
0x37 // 55
0b110111 + 0o67 // 110
(0b110111).toString() // 55
(0b110111).valueOf() //55原生进制转换
JavaScript 提供了原生函数,进行十进制与其他各进制之间的相互转换。
其中,从其他进制转换成十进制,有三种方式:parseInt(),Number(),+(一元运算符)。这三种方式都只能转换整数。
从十进制转换成其他进制,可以使用 Number.prototype.toString()。支持小数。
parseInt(str, radix)
parseInt()函数从字符串第一个非空格字符开始转换,如果第一个字符不是数值字符、加号或减号,则立即返回NaN。如果第一个字符是数值字符、加号或减号,则继续依次检测每个字符,直到字符串末尾或碰到非数值字符。比如“123blue”会被转换为123,因为“blue”会被忽略。类似的“22.5”会被转换为22,因为小数点不是有效的整数字符。
parseInt()接收两个参数。第一个参数是需要解析的字符串。
第二个参数是进制转换基数,表示转换时按什么进制来理解这个字符串,范围2~36,默认值10,表示转十进制。如果是非数字,则自动转数字,如无法转成数字则忽略该参数(忽略后使用默认的10进制)。
如果不传入第二参数,则 parseInt 会默认使用十进制来解析字符串;但是,如果字符串以0x开头,会被认为是十六进制数。而其他进制的字符串,0o67(八进制),0b110111(二进制) 不会以该进制基数自动转换,而是得到0。因此,在使用parseInt进行进制转换时,为了保证运行结果的正确性和稳定性,建议始终传入第二个参数。传入第一个参数后,第二个参数不再需要使用前缀。
// 二进制转十进制
parseInt("0b110111") // 0
parseInt("110111", 2) // 55
// 八进制转十进制
parseInt("0o67", 8) // 0
parseInt("67", 8) // 55
// 十进制
parseInt("55") // 55
parseInt("55", 10) // 55
// 十六进制转十进制
parseInt("0x37") // 55
parseInt("0x37", 16) // 55
parseInt("37", 16) // 55Number(str)
Number()可以识别出不同的进制格式,二进制、八进制、十六进制都可以识别。字符串中如果存在无效的进制字符时,返回 NaN。
Number("0b110111") // 55
Number("0o67") // 55
Number("55") // 55
Number("0x37") // 55
Number("0x37g") // NaN, 包含无效字符g+(一元运算符)
与 Number() 一样,可以把字符串转为数字,支持二进制、八进制、十六进制的字符串,默认转成十进制数字。
Number.prototype.toString(radix)
它支持传入一个进制转换基数,范围2~36,默认值为 10,用于将数字转换成对应进制的字符串,它支持转换小数。
15..toString(2) // 1111
585..toString(8) // 1111
4369..toString(16) // 1111
(11.25).toString(2) // 1011.01自定义转换
除了js提供的这些原生函数以外,也可以自己实现进制数字之间的转换函数。根据相应的规则,就可以实现各种进制之间转换的一些方法。
十进制与十六进制转换
十进制转十六进制:
十进制转十六进制,使用除基取余,倒叙排列法。
function int2Hex (num = 0) {
if (num === 0) {
return '0';
}
const HEXS = '0123456789abcdef';
let hex;
while (num) {
hex = HEXS.charAt(num % 16) + hex ;
num = Math.floor(num / 16) ;
}
return hex;
}十六进制转十进制:
十六进制转十进制,乘基加余,执行和十进制转十六进制相反的操作。
function hex2Int (hex = '') {
if (typeof hex !== 'string' || hex === '') {
return NaN;
}
const hexs = [...hex.toLowerCase()];
let resInt = 0;
for (let i = 0; i < hexs.length; i++) {
const hv = hexs[i];
let num = hv.charCodeAt() < 58 ? +hv : ((code - 97) + 10)
resInt = resInt * 16 + num;
}
return resInt;
}如果要转换八进制,转换方式与十六进制很类似,只需根据八进制的数值范围进行部分改动即可。
十进制和二进制转换
在十进制与二进制的转换中,我们将考虑小数。
十进制数字转换成二进制
将数值分成整数和小数两个部分,整数部分使用除基取余,倒叙排列法,小数部分使用乘基取整,正序排列法。
function c10to2 (num) {
// 整数
const numInteger = Math.floor(num);
// 小数
const numDecimal = num - numInteger;
let integers = [];
if (numInteger === 0) {
integers = ['0'];
} else {
let integerVal = numInteger;
while (integerVal !== 1) {
integers.push(integerVal % 2 === 0 ? '0' : '1');
integerVal = Math.floor(integerVal / 2);
}
integers.push('1');
}
const resInteger = integers.reverse().join('');
let decimals = [];
if (numDecimal) {
let decimalVal = numDecimal;
// 小数在转换成二进制时,会存在无限循环的问题,截取前53个值
let count = 53;
// 规格化时会左移,从第一位是1的地方开始记录
let startRecord = false;
while (decimalVal !== 1 && count > 0) {
decimalVal = decimalVal * 2;
if (decimalVal >= 1) {
startRecord = true;
decimals.push('1');
if (decimalVal > 1) {
decimalVal = decimalVal - 1;
}
} else {
decimals.push('0')
}
if (startRecord) {
count--;
}
}
}
const resDecimal = decimals.join('');
return resInteger + (resDecimal ? ('.' + resDecimal) : '');
}注意,小数在转换成二进制时,会存在无限循环的问题,上面的代码里截取了前53个值。
二进制数字转换成十进制
方法是:将二进制分成整数和小数两部分,分别进行转换,然后再组合成结果的十进制数值。
整数部分:这里直接使用 parseInt 函数,parseInt('1011', 2) => 11。
小数部分:如 1011.001 的小数位 001,使用按位权展开的多项式,0(2^-1) + 0(2^-2) + 1*(2^-3) = 0 + 0 + 0.125 = 0.125。
整数与小数合起来,就得到了 1011.001 的十进制数字:11.125。
function c2To10 (binaryStr = '') {
if (typeof binaryStr !== 'string' || binaryStr === '') {
return NaN;
}
const [ binIntStr, binDecStr ] = binaryStr.split('.');
let binDecimal = 0;
if (binDecStr) {
binDecimal = [...binDecStr].reduce((res, val, index) => {
res += Number(val) * (2 ** (-(index + 1)));
return res;
}, 0);
}
return parseInt(binIntStr, 2) + binDecimal;
}结语
篇幅有限,自定义转换部分没有将所有的转换规则罗列出来,可以参考我之前讲述进制转换规则的文章,举一反三自行实现。JS提供的四种进制为我们表示各种数值形式提供了很大的便利,除了十进制是Javascript默认的数字进制以外,其他三种进制方式平时使用较少,主要在处理Blob数据、字节编码或者位运算、转义字符等等时候才会碰到。各进制间通过一定的规则可以相互转换,这就让我们不用担心数据类型不一致的问题。