前言 在一般的代碼中很少會接觸到進位和位運算,但這不代表我們可以不去學習它。作為一位編程人員,這些都是基礎知識。如果你沒有學過這方面的知識,也不要慌,接下來的知識並不會很難。本文你將會學習到: 進位轉換 按位操作符 Javascript進位轉換 手動實現進位轉換 進位轉換 以下使用常見的十進位和二進 ...
前言
在一般的代碼中很少會接觸到進位和位運算,但這不代表我們可以不去學習它。作為一位編程人員,這些都是基礎知識。如果你沒有學過這方面的知識,也不要慌,接下來的知識並不會很難。本文你將會學習到:
- 進位轉換
- 按位操作符
- Javascript進位轉換
- 手動實現進位轉換
進位轉換
以下使用常見的十進位和二進位轉換作為例子,其他進位的轉換也是大同小異,感興趣可以自己琢磨下。
十進位轉二進位
根據 “逢十進一” 的法則進行計數時,每十個相同的單位組成一個和它相鄰的較高的單位,這種計數法叫做十進位計數法,簡稱十進位。這種是我們最常用的計數法。
整數
整數使用 “除二取餘,逆序排列” 來轉換為二進位,下麵是18轉換為二進位的例子:
// 除二取餘
18 / 2 = 9...0
9 / 2 = 4...1
4 / 2 = 2...0
2 / 2 = 1...0
1 / 2 = 0...1
// 倒序排列
10010
就這麼簡單,將得出的餘數逆序排列,即可得出18的二進位表示
小數
小數使用的是 “乘二取整,順序排列”,由於方法不同需要分開計算。下麵是16.125轉為二進位的例子:
16 / 2 = 8...0
8 / 2 = 4...0
4 / 2 = 2...0
2 / 2 = 1...0
1 / 2 = 0...1
0.125 * 2 = 0.25
0.25 * 2 = 0.5
0.5 * 2 = 1
10000.001
將小數相乘的結果,取結果的整數順序排列,得出小數位的二進位表示
二進位轉十進位
根據 “逢二進一 ” 的法則進行計數時,每兩個相同的單位組成一個和它相鄰的較高的單位,這種計數法叫做二進位計數 法,簡稱二進位。用二進位計數時,只需用兩個獨立的符號“0”和“1” 來表示。
整數
整數使用 “按權相加” 法,即二進位數首先寫成加權繫數展開式,然後按十進位加法規則求和。下麵是101010轉換位十進位的例子:
2^5 2^4 2^3 2^2 2^1 2^0
1 0 1 0 1 0
------------------------
32 + 0 + 8 + 0 + 2 + 0 = 42
上面從右數依次是2的0次方,2的1次方,2的2次方... , 只取位數為1的結果,將它們相加就可以得到十進位。
小數
10110.11轉十進位:
2^4 2^3 2^2 2^1 2^0 2^-1 2^-2
1 0 1 1 0 . 1 1
-------------------------------
16 + 0 + 4 + 2 + 0 + 0.5 + 0.25 = 22.75
按位操作符
按位操作符(Bitwise operators) 將其操作數(operands)當作32位的比特序列(由0和1組成),前 31 位表示整數的數值,第 32 位表示整數的符號,0 表示正數,1 表示負數。例如,十進位數18,用二進位表示則為10010。按位操作符操作數字的二進位形式,但是返回值依然是標準的JavaScript數值。
按位與( AND)
對於每一個比特位,只有兩個操作數相應的比特位都是1時,結果才為1,否則為0。
用法: a & b
。
9 (base 10) = 00000000000000000000000000001001 (base 2)
14 (base 10) = 00000000000000000000000000001110 (base 2)
--------------------------------
14 & 9 (base 10) = 00000000000000000000000000001000 (base 2) = 8 (base 10)
在判斷一個數字奇偶時,可以使用 a & 1
function assert(n) {
return n & 1 ? "奇數" : "偶數"
}
assert(3) // 奇數
因為奇數的二進位最後一位是1,而1的二進位最後一位也是1,通過 &
操作符得出結果為1
按位或(OR)
對於每一個比特位,當兩個操作數相應的比特位至少有一個1時,結果為1,否則為0。
用法: a | b
9 (base 10) = 00000000000000000000000000001001 (base 2)
14 (base 10) = 00000000000000000000000000001110 (base 2)
--------------------------------
14 | 9 (base 10) = 00000000000000000000000000001111 (base 2) = 15 (base 10)
將浮點數向下取整轉為整數,可以使用 a | 0
12.1 | 0 // 12
12.9 | 0 // 12
按位異或(XOR)
對於每一個比特位,當兩個操作數相應的比特位有且只有一個1時,結果為1,否則為0。
用法: a ^ b
9 (base 10) = 00000000000000000000000000001001 (base 2)
14 (base 10) = 00000000000000000000000000001110 (base 2)
--------------------------------
14 ^ 9 (base 10) = 00000000000000000000000000000111 (base 2) = 7 (base 10)
按位非(NOT)
反轉操作數的比特位,即0變成1,1變成0。
用法: ~ a
9 (base 10) = 00000000000000000000000000001001 (base 2)
--------------------------------
~9 (base 10) = 11111111111111111111111111110110 (base 2) = -10 (base 10)
通過兩次反轉操作,可將浮點數向下取整轉為整數
~~16.125 // 16
~~16.725 // 16
左移(Left shift)
將 a 的二進位形式向左移 b (< 32) 比特位,右邊用0填充。
用法: a << b
9 (base 10): 00000000000000000000000000001001 (base 2)
--------------------------------
9 << 2 (base 10): 00000000000000000000000000100100 (base 2) = 36 (base 10)
左移一位相當於在原數字基礎上乘2,利用這一特點,實現2的n次方:
function power(n) {
return 1 << n
}
power(3) // 8
有符號右移
將 a 的二進位表示向右移 b (< 32) 位,丟棄被移出的位。
用法: a >> b
9 (base 10): 00000000000000000000000000001001 (base 2)
--------------------------------
9 >> 2 (base 10): 00000000000000000000000000000010 (base 2) = 2 (base 10)
相比之下, -9 >> 2
得到 -3,因為符號被保留了。
-9 (base 10): 11111111111111111111111111110111 (base 2)
--------------------------------
-9 >> 2 (base 10): 11111111111111111111111111111101 (base 2) = -3 (base 10)
與左移相反,右移一位在原數字基礎上除以2
64 >> 1 // 32
無符號右移
將 a 的二進位表示向右移 b (< 32) 位,丟棄被移出的位,並使用 0 在左側填充。
用法: a >>> b
在非負數來說, 9 >>>2
和 9 >> 2
都是一樣的結果
9 (base 10): 00000000000000000000000000001001 (base 2)
--------------------------------
9 >>> 2 (base 10): 00000000000000000000000000000010 (base 2) = 2 (base 10)
而對於負數來說,結果就大有不同了,因為 >>>
不保留符號,當負數無符號右移時,會使用0填充
-9 (base 10): 11111111111111111111111111110111 (base 2)
--------------------------------
-9 >>> 2 (base 10): 00111111111111111111111111111101 (base 2) = 1073741821 (base 10)
可以使用無符號右移來判斷一個數的正負
function isPos(n) {
return (n === (n >>> 0)) ? true : false;
}
isPos(-1); // false
isPos(1); // true
雖然 -1 >>> 0
不會發生右移,但 -1 的二進位碼已經變成了正數的二進位碼, -1 >>> 0
結果為4294967295
Javascript進位轉換
toString
toString
常用於將一個變數轉為字元串,或是判斷一個變數的類型,例如:
let arr = []
Object.prototype.toString.call(arr) // [object Array]
你應該沒想過 toString
可以用於進位轉換,請看下麵例子:
(18).toString(2) // 10010(base 2)
(18).toString(8) // 22 (base 8)
(18).toString(16) // 12 (base 16)
參數規定表示數字的基數,是 2 ~ 36 之間的整數,若省略該參數,則使用基數 10。該參數可以理解為轉換後的進位表示。
parseInt
parseInt
常用於數字取整,它同樣可以傳入參數用於進位轉換,請看下麵例子:
parseInt(10010, 2) // 18 (base 10)
parseInt(22, 8) // 18 (base 10)
parseInt(12, 16) // 18 (base 10)
第二個參數表示要解析的數字的基數,該值介於 2 ~ 36 之間。如果省略該參數或其值為 0,則數字將以 10 為基礎來解析。如果該參數小於 2 或者大於 36,則 parseInt
將返回 NaN。
記得有道面試題是這樣的:
// 問:返回的結果
[1, 2, 3].map(paseInt)
接下來,我們來一步一步的看下過程發生了什麼?
parseInt(1, 0) // 基數為 0 時,以 10 為基數進行解析,結果為 1
parseInt(2, 1) // 基數不符合 2 ~ 36 的範圍,結果為 NaN
parseInt(3, 2) // 這裡以 2 為基數進行解析,但 3 很明顯不是一個二進位表示,故結果為 NaN
//題目結果為
[1, NaN, NaN]
手動實現進位轉換
雖然 JavaScript
為我們內置了進位轉換的函數,但手動實現進位轉換有利於我們理解過程,提高邏輯能力。對於初學者來說也是一個很不錯的練習例子。以下只簡單實現非負整數的轉換。
十進位轉二進位
基於 “除二取餘” 思路實現
function toBinary(value) {
if (isNaN(Number(value))) {
throw `${value} is not a number`
}
let bits = []
while (value >= 1) {
bits.unshift(value % 2)
value = Math.floor(value / 2)
}
return bits.join('')
}
使用
toBinary(36) // 100100
toBinary(12) // 1100
二進位轉十進位
基於 “按權相加” 思路實現
function toDecimal(value) {
let bits = value.toString().split('')
let res = 0
while (bits.length) {
let bit = bits.shift()
if (bit == 1) {
// ** 為冪運算符,如:2**3 為 8
res += 2 ** bits.length
}
}
return res
}
使用
toDecimal(10011) // 19
toDecimal(11111) // 33
寫在最後
本文為大家介紹了進位和位運算的相關知識,旨在溫故知新。我們只需要大概瞭解就好,因為在開發中真的用得少,至少我只用過 ~~
來取整。而類似於~~
這種取整操作還是儘量少用為好,對於其他開發者來說,可能會影響到代碼可讀性。