一文帶你搞懂JavaScript中的進(jìn)制與進(jìn)制轉(zhuǎn)換
進(jìn)制介紹
JavaScript
中提供的進(jìn)制表示方法有四種:十進(jìn)制、二進(jìn)制、十六進(jìn)制、八進(jìn)制。
對(duì)于數(shù)值字面量,主要使用不同的前綴來區(qū)分:
- 十進(jìn)制(Decimal):取值數(shù)字
0-9
;不用前綴。 - 二進(jìn)制(Binary):取值數(shù)字
0
和1
;前綴0b
或0B
。 - 十六進(jìn)制(Hexadecimal):取值數(shù)字
0-9
和a-f
;前綴0x
或0X
。 - 八進(jìn)制(Octal):取值數(shù)字
0-7
;前綴0o
或0O
(ES6規(guī)定)。
需要注意的是,非嚴(yán)格模式下瀏覽器支持:如果有前綴0并且后面只用到 0-7
八個(gè)數(shù)字的數(shù)值時(shí),該數(shù)值視為八進(jìn)制;但如果前綴0后面跟隨的數(shù)字中有 8或者9
,則視為十進(jìn)制。
嚴(yán)格模式下,如果數(shù)字加前綴0,則報(bào)錯(cuò):Uncaught SyntaxError: Decimals with leading zeros are not allowed in strict mode。
各進(jìn)制的數(shù)值,如果取值數(shù)字超過給定的范圍,則會(huì)報(bào)錯(cuò):Uncaught SyntaxError: Invalid or unexpected token。
在JavaScript內(nèi)部的默認(rèn)情況下,二進(jìn)制、十六進(jìn)制、八進(jìn)制字面量數(shù)值,都會(huì)自動(dòng)轉(zhuǎn)為十進(jìn)制進(jìn)行運(yùn)算。
0x22 // 34 0b111 // 7 0o33 // 27 0x22 + 0b111 // 41 0o33 + 12 // 39 (0x33).toString() // 51 (0x33).valueOf() // 51
除了十進(jìn)制是Javascript默認(rèn)的數(shù)字進(jìn)制以外,其他三種進(jìn)制方式平時(shí)使用較少,主要在處理Blob數(shù)據(jù)、字節(jié)編碼或者位運(yùn)算、轉(zhuǎn)義字符等等時(shí)候才會(huì)碰到。
進(jìn)制轉(zhuǎn)換
下文將主要討論進(jìn)制轉(zhuǎn)換時(shí)的問題。
JavaScript 提供了原生函數(shù),進(jìn)行十進(jìn)制與其他各進(jìn)制之間的相互轉(zhuǎn)換。
其中,從其他進(jìn)制轉(zhuǎn)換成十進(jìn)制,有三種方式:parseInt()
,Number()
,+
(一元運(yùn)算符)。這三種方式都只能轉(zhuǎn)換整數(shù)。
從十進(jìn)制轉(zhuǎn)換成其他進(jìn)制,可以使用 Number.prototype.toString()
。支持小數(shù)。
parseInt(str, radix)
第一個(gè)參數(shù)是需要解析的字符串;其他進(jìn)制不加前綴。
第二個(gè)參數(shù)是一個(gè)進(jìn)制基數(shù),表示轉(zhuǎn)換時(shí)按什么進(jìn)制來理解這個(gè)字符串,默認(rèn)值10,表示轉(zhuǎn)十進(jìn)制。
第二個(gè)參數(shù)如果非數(shù)字,則自動(dòng)轉(zhuǎn)數(shù)字,如無法轉(zhuǎn)稱數(shù)字則忽略該參數(shù);是數(shù)字時(shí),必須是 2-36
的整數(shù),超出該范圍,返回 NaN
。
parseInt('1111', 2) // 15 parseInt('1234', 8) // 668 parseInt('18af', 16) // 6319 parseInt('1111') // 1111
如果不傳入第二參數(shù),則 parseInt
會(huì)默認(rèn)使用十進(jìn)制來解析字符串;但是,如果字符串以 0x
開頭,會(huì)被認(rèn)為是十六進(jìn)制數(shù)。
而其他進(jìn)制的字符串,0o21(八進(jìn)制)
,0b11(二進(jìn)制)
不會(huì)以該進(jìn)制基數(shù)自動(dòng)轉(zhuǎn)換,而是得到 0
。
所以,在使用 parseInt
進(jìn)行進(jìn)制轉(zhuǎn)換時(shí),為了保證運(yùn)行結(jié)果的正確性和穩(wěn)定性,第二個(gè)參數(shù)不能省略。
parseInt('0x21') // 33 parseInt('0o21') // 0 parseInt('0b11') // 0 parseInt('111', 'add') // 111 parseInt('111', '787') // NaN
如果需要解析的字符串中存在對(duì)于當(dāng)前進(jìn)制基數(shù)無效的字符,則會(huì)從最高位取有效字符進(jìn)行轉(zhuǎn)換,沒有效字符則返回NaN
。
parseInt('88kk', 16) // 136,=== 0x88 parseInt('kk', 16) // NaN
Number()
可以把字符串轉(zhuǎn)為數(shù)字,支持其他進(jìn)制的字符串,默認(rèn)轉(zhuǎn)成十進(jìn)制數(shù)字。
字符串中如果存在無效的進(jìn)制字符時(shí),返回 NaN
。
記住,需要使用進(jìn)制前綴,0b
,0o
,0x
。
Number('0b11100') // 28 Number('0o33') // 27 Number('0x33') //51 Number('0x88kk') // NaN
+(一元運(yùn)算符)
與 Number()
一樣,可以把字符串轉(zhuǎn)為數(shù)字,支持其他進(jìn)制的字符串,默認(rèn)轉(zhuǎn)成十進(jìn)制數(shù)字。
字符串中如果存在無效的進(jìn)制字符時(shí),返回 NaN
。
也需要使用進(jìn)制前綴。
+'0b11100' // 28 +'0o33' // 27 +'0x33' //51 +'0x88kk' // NaN
可以看到,基本和 Number()
是一樣的,也在本質(zhì)上是對(duì)數(shù)字的一種轉(zhuǎn)換處理。
Number.prototype.toString(radix)
它支持傳入一個(gè)進(jìn)制基數(shù),用于將數(shù)字轉(zhuǎn)換成對(duì)應(yīng)進(jìn)制的字符串,它支持轉(zhuǎn)換小數(shù)。
未指定默認(rèn)值為 10
,基數(shù)參數(shù)的范圍 2-36
,超過范圍,報(bào)錯(cuò):RangeError。
15..toString(2) // 1111 585..toString(8) // 1111 4369..toString(16) // 1111 (11.25).toString(2) // 1011.01
自定義轉(zhuǎn)換
除了這些原生函數(shù)以外,也可以自己實(shí)現(xiàn)進(jìn)制數(shù)字之間的轉(zhuǎn)換函數(shù)。
根據(jù)相應(yīng)的規(guī)則,就可以實(shí)現(xiàn)十進(jìn)制與二進(jìn)制、十六進(jìn)制之間的轉(zhuǎn)換的一些方法。
十進(jìn)制與十六進(jìn)制轉(zhuǎn)換
以下代碼是針對(duì)整數(shù)在十進(jìn)制與十六進(jìn)制之間的轉(zhuǎn)換,根據(jù)基本規(guī)則進(jìn)行換算。
十六進(jìn)制是以 0-9
、a-f
進(jìn)行描述數(shù)字的一種方式,其中 0-9
取本身數(shù)字的值,而 a-f
則取 10-15
的值。
且字母不區(qū)分大小寫。
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 }
如果要轉(zhuǎn)換八進(jìn)制,實(shí)際上與十六進(jìn)制很類似,只需根據(jù)八進(jìn)制的數(shù)值范圍進(jìn)行部分改動(dòng)即可。
八進(jìn)制一般使用非常少,不單獨(dú)列出。
下面將重點(diǎn)介紹二進(jìn)制轉(zhuǎn)換的相關(guān)知識(shí),包括小數(shù)的二進(jìn)制表示與轉(zhuǎn)換。
十進(jìn)制和二進(jìn)制轉(zhuǎn)換
在十進(jìn)制與二進(jìn)制的轉(zhuǎn)換中,我們將考慮小數(shù),理解小數(shù)是如何在這兩者之間進(jìn)行轉(zhuǎn)換。
先選定一個(gè)數(shù)字,比如:11.125
,我們看下該數(shù)字在二進(jìn)制里的表示:
(11.125).toString(2) // 1011.001
可以看到,11.125
的二進(jìn)制表示為:1011.001
。下面將以這個(gè)數(shù)字為例進(jìn)行轉(zhuǎn)換操作。
十進(jìn)制數(shù)字轉(zhuǎn)換成二進(jìn)制
首先需要了解的是,二進(jìn)制小數(shù)的表示方法是如何得來的:
整數(shù) 部分,用二進(jìn)制表示可以如此計(jì)算,數(shù)字 11
:
11 / 2 ———— 1
5 / 2 ———— 1
2 / 2 ———— 0
1 / 2 ———— 1
整數(shù)部分的規(guī)則,得到的結(jié)果是 從下往上,倒著排 1011
就是二進(jìn)制的 11
。
小數(shù) 用二進(jìn)制表示可以如此計(jì)算,小數(shù) 0.125
:
0.125 × 2 = 0.25 ———— 0
0.25 × 2 = 0.5 ———— 0
0.5 × 2 = 1 ———— 1
只有等于1時(shí)才結(jié)束,如果結(jié)果不等于1將會(huì)一直循環(huán)下去。小數(shù)部分的規(guī)則,得到的結(jié)果是 從上往下,順著排 0.001
就是二進(jìn)制的 0.125
。
整數(shù) + 小數(shù),所以 11.125
的二進(jìn)制表示方式:1011.001
。
根據(jù)以上整數(shù)和小數(shù)分開計(jì)算的規(guī)則,就可以得到一個(gè)十進(jìn)制轉(zhuǎn)二進(jìn)制的函數(shù),如下:
function c10to2 (num) { // 整數(shù) const numInteger = Math.floor(num) // 小數(shù) 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 // 最多取49位的長度 let count = 49 while (decimalVal !== 1 && count > 0) { decimalVal = decimalVal * 2 if (decimalVal >= 1) { decimals.push('1') if (decimalVal > 1) { decimalVal = decimalVal - 1 } } else { decimals.push('0') } count-- } } const resDecimal = decimals.join('') return resInteger + (resDecimal ? ('.' + resDecimal) : '') }
小數(shù)在轉(zhuǎn)換成二進(jìn)制時(shí),會(huì)存在無限循環(huán)的問題,上面的代碼里截取了前49個(gè)值。
所以,這里就會(huì)引出了一個(gè)問題,就是常見的一個(gè)數(shù)字精度問題:0.1 + 0.2 != 0.3
。
0.1+ 0.2 != 0.3
直接看一下 0.1
轉(zhuǎn)二進(jìn)制:
0.1 × 2 = 0.2
0.2 × 2 = 0.4
0.4 × 2 = 0.8
0.8 × 2 = 1.6
0.6 × 2 = 1.2
0.2 × 2 = 0.4 // 循環(huán)開始
0.4 × 2 = 0.8
0.8 × 2 = 1.6
0.6 × 2 = 1.2
...
...
無限循環(huán)
0.2
轉(zhuǎn)二進(jìn)制:
0.2 × 2 = 0.4
0.4 × 2 = 0.8
0.8 × 2 = 1.6
0.6 × 2 = 1.2
0.2 × 2 = 0.4 // 循環(huán)開始
0.4 × 2 = 0.8
0.8 × 2 = 1.6
0.6 × 2 = 1.2
...
... 無限循環(huán)
因?yàn)闊o法得到1,可以發(fā)現(xiàn)有限十進(jìn)制小數(shù), 0.1
轉(zhuǎn)換成了無限二進(jìn)制小數(shù) 0.00011001100...
,0.2
轉(zhuǎn)成了 0.001100110011...
。
由于無限循環(huán),必然會(huì)導(dǎo)致精度丟失,正好 0.1 + 0.2
計(jì)算得到的數(shù)字在丟失精度后的最后一位不為0,所以導(dǎo)致結(jié)果為:0.30000000000000004
。
如果截取精度后最后一位為0,那自然就不存在結(jié)果不相等的情況,如 0.1 + 0.6 === 0.7
,事實(shí)上,0.1和0.6轉(zhuǎn)二進(jìn)制后都會(huì)丟失精度,但截取到的數(shù)值都是0,所以相等。
同樣不相等的還設(shè)有 0.1 + 0.7 !== 0.8
等等。
所以是計(jì)算時(shí)轉(zhuǎn)二進(jìn)制的精度丟失,才導(dǎo)致的 0.1 + 0.2 !== 0.3
。
在 JavaScript 中所有數(shù)值都以 IEEE-754 標(biāo)準(zhǔn)的 64 bit 雙精度浮點(diǎn)數(shù)進(jìn)行存儲(chǔ)的。 IEEE 754 標(biāo)準(zhǔn)的 64 位雙精度浮點(diǎn)數(shù)的小數(shù)部分最多支持53位二進(jìn)制位。 因浮點(diǎn)數(shù)小數(shù)位的限制而需要先截?cái)喽M(jìn)制數(shù)字,再轉(zhuǎn)換為十進(jìn)制,所以在進(jìn)行算術(shù)計(jì)算時(shí)會(huì)產(chǎn)生誤差。
這里能看到,如果十進(jìn)制小數(shù)要被轉(zhuǎn)化為有限二進(jìn)制小數(shù),那么它計(jì)算后的小數(shù)第一位數(shù)必然要是 5
結(jié)尾才行(因?yàn)橹挥?0.5 × 2
才能變?yōu)檎麛?shù))。
二進(jìn)制數(shù)字轉(zhuǎn)換成十進(jìn)制
方法是:將二進(jìn)制分成整數(shù)和小數(shù)兩部分,分別進(jìn)行轉(zhuǎn)換,然后再組合成結(jié)果的十進(jìn)制數(shù)值。
整數(shù)部分:這里直接使用 parseInt
函數(shù),parseInt('1011', 2) => 11
。
小數(shù)部分:如 1011.001
的小數(shù)位 001
,使用下表的計(jì)算方式。
小數(shù)部分 | 0 | 0 | 1 |
---|---|---|---|
基數(shù)的位數(shù)次冪 | 2^-1 | 2^-2 | 2^-3 |
每位與基數(shù)乘積 | 0 × (2^-1) | 0 × (2^-2) | 1×(2^-3) |
每位乘積結(jié)果 | 0 | 0 | 0.125 |
最后的結(jié)果是每位乘積結(jié)果相加:0 + 0 + 0.125 = 0.125
。
整數(shù)與小數(shù)合起來,就得到了 1011.001
的十進(jìn)制數(shù)字:11.125
。
根據(jù)規(guī)則,代碼實(shí)現(xiàn)如下所示:
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 }
到此這篇關(guān)于一文帶你搞懂JavaScript中的進(jìn)制與進(jìn)制轉(zhuǎn)換的文章就介紹到這了,更多相關(guān)JavaScript進(jìn)制轉(zhuǎn)換內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS面向?qū)ο缶幊袒A(chǔ)篇(一) 對(duì)象和構(gòu)造函數(shù)實(shí)例詳解
這篇文章主要介紹了JS面向?qū)ο缶幊虒?duì)象和構(gòu)造函數(shù),結(jié)合實(shí)例形式詳細(xì)分析了JS面向?qū)ο缶幊虒?duì)象和構(gòu)造函數(shù)具體概念、原理、使用方法及操作注意事項(xiàng),需要的朋友可以參考下2020-03-03《javascript設(shè)計(jì)模式》學(xué)習(xí)筆記五:Javascript面向?qū)ο蟪绦蛟O(shè)計(jì)工廠模式實(shí)例分析
這篇文章主要介紹了Javascript面向?qū)ο蟪绦蛟O(shè)計(jì)工廠模式,結(jié)合實(shí)例形式分析了《javascript設(shè)計(jì)模式》中Javascript面向?qū)ο蠊S模式相關(guān)概念、原理、用法及操作注意事項(xiàng),需要的朋友可以參考下2020-04-04JavaScript函數(shù)中的防抖與節(jié)流原生實(shí)現(xiàn)及第三方庫的使用
當(dāng)你頻繁的觸發(fā)用戶界面時(shí),會(huì)不停的觸發(fā)事件處理函數(shù),可能導(dǎo)致界面卡頓,瀏覽器奔潰,頁面空白等情況,而解決這一問題的,正是函數(shù)節(jié)流與函數(shù)防抖,所以本文將給大家介紹一下JavaScript函數(shù)中的防抖與節(jié)流原生實(shí)現(xiàn)及第三方庫的使用,需要的朋友可以參考下2023-10-10