摘要:原碼補碼和反碼原碼一個數(shù)在計算機中是以二進制的形式存在的,其中第一位存放符號正數(shù)為負數(shù)為。中的位運算在中按位操作符會將其操作數(shù)轉(zhuǎn)成補碼形式的有符號位整數(shù)。原文鏈接由扯到中的位運算
這個話題的由來是2016年3月份的時候 NPM 社區(qū)發(fā)生了‘left-pad’事件,不久后社區(qū)就有人發(fā)布了用來補救的,也是現(xiàn)在大家能用到的 left-pad 庫。
最開始這個庫的代碼是這樣的。
module.exports = leftpad; function leftpad (str, len, ch) { str = String(str); var i = -1; if (!ch && ch !== 0) ch = " "; len = len - str.length; while (++i < len) { str = ch + str; } return str; }
我第一次看到這段代碼的時候,沒看出什么毛病,覺得清晰明了。后來刷微博的時候@左耳朵耗子老師指出了這段代碼可以寫得更有效率些,于是他就貼出了自己寫的版本并給 left-pad 提了 PR,代碼如下:
module.exports = leftpad; function leftpad (str, len, ch) { //convert the `str` to String str = str +""; //needn"t to pad len = len - str.length; if (len <= 0) return str; //convert the `ch` to String if (!ch && ch !== 0) ch = " "; ch = ch + ""; var pad = ""; while (true) { if (len & 1) pad += ch; len >>= 1; if (len) ch += ch; else break; } return pad + str; }
我當(dāng)時看到他的這段代碼的里面的 &和>>運算符的時候一下有點懵了,只知道這是位運算里面的‘按位與’和‘右移’運算,但是完全不知道為什么這樣寫就能提高效率。于是就想著去了解位運算的實質(zhì)和使用場景。
在了解位運算之前,我們先必須了解一下什么是原碼、反碼和補碼以及二進制與十進制的轉(zhuǎn)換。
原碼、補碼和反碼 原碼一個數(shù)在計算機中是以二進制的形式存在的,其中第一位存放符號, 正數(shù)為0, 負數(shù)為1。原碼就是用第一位存放符號的二進制數(shù)值。例如2的原碼為00000010,-2的原碼為10000010。
反碼正數(shù)的反碼是其本身。負數(shù)的反碼是在其原碼的基礎(chǔ)上,符號位不變,其余各位取反,即0變1,1變0。
[+3]=[00000011]原=[00000011]反 [-3]=[10000011]原=[11111100]反
可見如果一個反碼表示的是負數(shù),并不能直觀的看出它的數(shù)值,通常要將其轉(zhuǎn)換成原碼再計算。
補碼正數(shù)的補碼是其本身。負數(shù)的補碼是在其原碼的基礎(chǔ)上,符號位不變,其余各位取反,最后+1。(即負數(shù)的補碼為在其反碼的基礎(chǔ)上+1)。
[+3]=[00000011]原=[00000011]反=[00000011]補 [-3]=[10000011]原=[11111100]反=[11111101]補
可見對于負數(shù),補碼的表示方式也是讓人無法直觀看出其數(shù)值的,通常也需要轉(zhuǎn)換成原碼再計算。
二進制與十進制的轉(zhuǎn)換二進制與十進制的區(qū)別在于數(shù)運算時是逢幾進一位。二進制是逢2進一位,十進制也就是我們常用的0-9是逢10進一位
正整數(shù)的十進制轉(zhuǎn)二進制正整數(shù)的十進制轉(zhuǎn)二進制的方法為將一個十進制數(shù)除以2,得到的商再除以2,以此類推直到商等于1或0時為止,倒取除得的余數(shù),即為轉(zhuǎn)換所得的二進制數(shù)的結(jié)果。
例如把52換算成二進制數(shù),計算過程如下圖:
52除以2得到的余數(shù)依次為:0、0、1、0、1、1,倒序排列,所以52對應(yīng)的二進制數(shù)就是110100。
負整數(shù)的十進制轉(zhuǎn)二進制為將該負整數(shù)對應(yīng)的正整數(shù)先轉(zhuǎn)換成二進制,然后對其“取反”,再對取反后的結(jié)果+1。即負整數(shù)采用其二進制補碼的形式存儲。
至于負數(shù)為什么要用二進制補碼的形式存儲,可參考一篇阮一峰的文章《關(guān)于2的補碼》。
例如 -52 的原碼為 10110100,其反碼為 11001011,其補碼為 11001100。所以 -52 轉(zhuǎn)換為二進制后為 11001100。
十進制小數(shù)轉(zhuǎn)二進制的方法為“乘2取整”,對十進制小數(shù)乘2得到的整數(shù)部分和小數(shù)部分,整數(shù)部分即是相應(yīng)的二進制數(shù)碼,再用2乘小數(shù)部分(之前乘后得到新的小數(shù)部分),又得到整數(shù)和小數(shù)部分。
如此不斷重復(fù),直到小數(shù)部分為0或達到精度要求為止。第一次所得到為最高位,最后一次得到為最低位。
如:0.25的二進制 0.25*2=0.5 取整是0 0.5*2=1.0 取整是1 即0.25的二進制為 0.01 ( 第一次所得到為最高位,最后一次得到為最低位) 0.8125的二進制 0.8125*2=1.625 取整是1 0.625*2=1.25 取整是1 0.25*2=0.5 取整是0 0.5*2=1.0 取整是1 即0.8125的二進制是0.1101二進制轉(zhuǎn)十進制
從最后一位開始算,依次列為第0、1、2...位,第n位的數(shù)(0或1)乘以2的n次方,將得到的結(jié)果相加就是得到的十進制數(shù)。
例如二進制為110的數(shù),將其轉(zhuǎn)為十進制的過程如下
個位數(shù) 0 與 2o 相乘:0 × 2o = 0
十位數(shù) 1 與 21 相乘:1 × 21 = 2
百位數(shù) 1 與 22 相乘:1 × 22 = 4
將得到的結(jié)果相加:0+2+4=6
所以二進制 110 轉(zhuǎn)換為十進制后的數(shù)值為 6。
小數(shù)二進制用數(shù)值乘以2的負冪次然后相加。
JavaScript 中的位運算在 ECMAScript 中按位操作符會將其操作數(shù)轉(zhuǎn)成補碼形式的有符號32位整數(shù)。下面是ECMAScript 規(guī)格中對于位運算的執(zhí)行過程的表述:
The production A : A @ B, where @ is one of the bitwise operators in the productions above, is evaluated as follows: 1. Let lref be the result of evaluating A. 2. Let lval be GetValue(lref). 3. ReturnIfAbrupt(lval). 4. Let rref be the result of evaluating B. 5. Let rval be GetValue(rref). 6. ReturnIfAbrupt(rval). 7. Let lnum be ToInt32(lval). 8. ReturnIfAbrupt(lnum). 9. Let rnum be ToInt32(rval). 10. ReturnIfAbrupt(rnum). 11. Return the result of applying the bitwise operator @ to lnum and rnum. The result is a signed 32 bit integer.
需要注意的是第七步和第九步,根據(jù) ES 的標(biāo)準(zhǔn),超過32位的整數(shù)會被截斷,而小數(shù)部分則會被直接舍棄。所以由此可以知道,在 JS 中,當(dāng)位運算中有操作數(shù)大于或等于232時,就會出現(xiàn)意想不到的結(jié)果。
JavaScript 中的位運算有:&(按位與)、|(按位或)、~(取反)、^(按位異或)、<<(左移)、>>(有符號右移)和>>>(無符號右移)。
&按位與對每一個比特位執(zhí)行與(AND)操作。只有 a 和 b 都是 1 時,a & b 才是 1。
例如:9(base 10) & 14(base 10) = 1001(base2) & 1110(base 2) = 1000(base 2) = 8(base 10)
因為當(dāng)只有 a 和 b 都是 1 時,a&b才等于1,所以任一數(shù)值 x 與0(二進制的每一位都是0)按位與操作,其結(jié)果都為0。將任一數(shù)值 x 與 -1(二進制的每一位都是1)按位與操作,其結(jié)果都為 x。
利用 & 運算的特點,我們可以用以簡單的判斷奇偶數(shù),公式:
(n & 1) === 0 //true 為偶數(shù),false 為奇數(shù)。
因為 1 的二進制只有最后一位為1,其余位都是0,所以其判斷奇偶的實質(zhì)是判斷二進制數(shù)最后一位是 0 還是 1。奇數(shù)的二進制最后一位是 1,偶數(shù)是0。
當(dāng)然還可以利用 JS 在做位運算時會舍棄掉小數(shù)部分的特性來做向下取整的運算,因為當(dāng) x 為整數(shù)時有 x&-1=x,所以當(dāng) x 為小數(shù)時有 x&-1===Math.floor(x)。
|按位或對每一個比特位執(zhí)行或(OR)操作。如果 a 或 b 為 1,則 a | b 結(jié)果為 1。
例如:9(base 10) | 14(base 10) = 1001(base2) | 1110(base 2) = 1111(base 2) = 15(base 10)
因為只要 a 或 b 其中一個是 1 時,a|b就等于1,所以任一數(shù)值 x 與-1(二進制的每一位都是1)按位與操作,其結(jié)果都為-1。將任一數(shù)值 x 與 0(二進制的每一位都是0)按位與操作,其結(jié)果都為 x。
同樣,按位或也可以做向下取整運算,因為當(dāng) x 為整數(shù)時有 x|0=x,所以當(dāng) x 為小數(shù)時有 x|0===Math.floor(x)。
~取反對每一個比特位執(zhí)行非(NOT)操作。~a 結(jié)果為 a 的反轉(zhuǎn)(即反碼)。
9 (base 10) = 00000000000000000000000000001001 (base 2) -------------------------------- ~9 (base 10) = 11111111111111111111111111110110 (base 2) = -10 (base 10)
負數(shù)的二進制轉(zhuǎn)化為十進制的規(guī)則是,符號位不變,其他位取反后加 1。
對任一數(shù)值 x 進行按位非操作的結(jié)果為 -(x + 1)。~~x === x。
同樣,取反也可以做向下取整運算,因為當(dāng) x 為整數(shù)時有 ~~x===x,所以當(dāng) x 為小數(shù)時有 ~~x===Math.floor(x)。
^按位異或對每一對比特位執(zhí)行異或(XOR)操作。當(dāng) a 和 b 不相同時,a ^ b 的結(jié)果為 1。
例如:9(base 10) ^ 14(base 10) = 1001(base2) ^ 1110(base 2) = 0111(base 2) = 7(base 10)
將任一數(shù)值 x 與 0 進行異或操作,其結(jié)果為 x。將任一數(shù)值 x 與 -1 進行異或操作,其結(jié)果為 ~x,即 x^-1=~x。
同樣,按位異或也可以做向下取整運算,因為當(dāng) x 為整數(shù)時有 (x^0)===x,所以當(dāng) x 為小數(shù)時有 (x^0)===Math.floor(x)。
它把數(shù)字中的所有數(shù)位向左移動指定的數(shù)量,向左被移出的位被丟棄,右側(cè)用 0 補充。
例如,把數(shù)字 2(等于二進制中的 10)左移 5 位,結(jié)果為 64(等于二進制中的 1000000):
var iOld = 2; //等于二進制 10 var iNew = iOld << 5; //等于二進制 1000000 十進制 64
因為二進制10轉(zhuǎn)換成十進制的過程為 1×21+0×2o,在運算中2的指數(shù)與位置數(shù)相對應(yīng),當(dāng)左移五位后就變成了 1×21??+0×2o??= 1×21×2?+0×2o×2? = (1×21+0×2o)×2?。所以由此可以看出當(dāng)2左移五位就變成了 2×2?=64。
所以有一個數(shù)左移 n 為,即為這個數(shù)乘以2的n次方。x<
同樣,左移運算也可以做向下取整運算,因為當(dāng) x 為整數(shù)時有 (x<<0)===x,所以當(dāng) x 為小數(shù)時有 (x<<0)===Math.floor(x)。
它把 32 位數(shù)字中的所有數(shù)位整體右移,同時保留該數(shù)的符號(正號或負號)。有符號右移運算符恰好與左移運算相反。例如,把 64 右移 5 位,將變?yōu)?2。
因為有符號右移運算符與左移運算相反,所以有一個數(shù)左移 n 為,即為這個數(shù)除以2的n次方。x<
同樣,有符號右移運算也可以做向下取整運算,因為當(dāng) x 為整數(shù)時有 (x>>0)===x,所以當(dāng) x 為小數(shù)時有 (x>>0)===Math.floor(x)。
它將無符號 32 位數(shù)的所有數(shù)位整體右移。對于正數(shù),無符號右移運算的結(jié)果與有符號右移運算一樣,而負數(shù)則被作為正數(shù)來處理。
-9 (base 10): 11111111111111111111111111110111 (base 2) -------------------------------- -9 >>> 2 (base 10): 00111111111111111111111111111101 (base 2) = 1073741821 (base 10)
根據(jù)無符號右移的正數(shù)右移與有符號右移運算一樣,而負數(shù)的無符號右移一定為非負的特征,可以用來判斷數(shù)字的正負,如下:
function isPos(n) { return (n === (n >>> 0)) ? true : false; } isPos(-1); // false isPos(1); // true總結(jié)
根據(jù) JS 的位運算,可以得出如下信息:
1、所有的位運算都可以對小數(shù)取底。
2、對于按位與&,可以用 (n & 1) === 0 //true 為偶數(shù),false 為奇數(shù)。來判斷奇偶。用x&-1===Math.floor(x)來向下取底。
3、對于按位或|,可以用x|0===Math.floor(x)來向下取底。
4、對于取反運算~,可以用~~x===Math.floor(x)來向下取底。
5、對于異或運算^,可以用(x^0)===Math.floor(x)來向下取底。
6、對于左移運算<<,可以x<
7、對于有符號右移運算>>,可以x<
8、對于無符號右移運算>>>,可以(n === (n >>> 0)) ? true : false;來判斷數(shù)字正負,用x>>>0===Math.floor(x)來向下取底。
用移位運算來替代普通算術(shù)能獲得更高的效率。移位運算翻譯成機器碼的長度更短,執(zhí)行更快,需要的硬件開銷更小。
原文鏈接:由left-pad扯到JS中的位運算
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/88865.html
摘要:雖然在內(nèi)部,數(shù)值都是以位浮點數(shù)的形式儲存,但是做位運算的時候,是以位帶符號的整數(shù)進行運算的,并且返回值也是一個位帶符號的整數(shù)。如下表應(yīng)用場景取整對于一般的整數(shù),返回值不會有任何變化。例如,結(jié)果為負數(shù)存儲采用的形式是二進制補碼。 什么是位運算? 位運算是在數(shù)字底層(即表示數(shù)字的 32 個數(shù)位)進行運算的。由于位運算是低級的運算操作,所以速度往往也是最快的(相對其它運算如加減乘除來說),并...
摘要:雖然在內(nèi)部,數(shù)值都是以位浮點數(shù)的形式儲存,但是做位運算的時候,是以位帶符號的整數(shù)進行運算的,并且返回值也是一個位帶符號的整數(shù)。如下表應(yīng)用場景取整對于一般的整數(shù),返回值不會有任何變化。例如,結(jié)果為負數(shù)存儲采用的形式是二進制補碼。 什么是位運算? 位運算是在數(shù)字底層(即表示數(shù)字的 32 個數(shù)位)進行運算的。由于位運算是低級的運算操作,所以速度往往也是最快的(相對其它運算如加減乘除來說),并...
摘要:雖然在內(nèi)部,數(shù)值都是以位浮點數(shù)的形式儲存,但是做位運算的時候,是以位帶符號的整數(shù)進行運算的,并且返回值也是一個位帶符號的整數(shù)。如下表應(yīng)用場景取整對于一般的整數(shù),返回值不會有任何變化。例如,結(jié)果為負數(shù)存儲采用的形式是二進制補碼。 什么是位運算? 位運算是在數(shù)字底層(即表示數(shù)字的 32 個數(shù)位)進行運算的。由于位運算是低級的運算操作,所以速度往往也是最快的(相對其它運算如加減乘除來說),并...
摘要:也就是說不僅是會產(chǎn)生這種問題,只要是采用的浮點數(shù)編碼方式來表示浮點數(shù)時,則會產(chǎn)生這類問題。到這里我們都理解只要采取的浮點數(shù)編碼的語言均會出現(xiàn)上述問題,只是它們的標(biāo)準(zhǔn)類庫已經(jīng)為我們提供了解決方案而已。 Brief 一天有個朋友問我JS中計算0.7 * 180怎么會等于125.99999999998,坑也太多了吧!那時我猜測是二進制表示數(shù)值時發(fā)生round-off error所導(dǎo)致,但并不...
摘要:作者源地址最近一個事件搞得圈沸沸揚揚的我們暫且把這個事情放一邊來看看本身的實現(xiàn)的源碼如下這段程序的作用是給一個字符串或可以轉(zhuǎn)成的變量用字符在左邊補位將其補到長度為當(dāng)然這個程序沒做充足的參數(shù)檢查這個就不細說了我們分析一下它的效率如果 作者: @flowmemo源地址: http://flowmemo.github.io/2016/03/25/str... 最近一個left-pad事件搞得...
閱讀 1365·2021-11-24 09:39
閱讀 2290·2021-11-22 13:54
閱讀 2347·2021-09-08 10:45
閱讀 1545·2021-08-09 13:43
閱讀 3057·2019-08-30 15:52
閱讀 3181·2019-08-29 15:38
閱讀 2972·2019-08-26 13:44
閱讀 3140·2019-08-26 13:30