JS魔法堂:再識Bitwise Operation & Bitwise Shift


Brief                              

  linkFly的《JavaScript-如果...沒有方法》中提及如何手寫Math.round方法,各種奇技淫招看着十分過癮,最讓我驚嘆的是 ~~(x + 0.5 + (x >> 30)) ,完全通過加法和位運算搞定整數的四舍五入。在好奇心的驅使下重溫了一下位運算,並對上述公式加以封裝得到適合小數的四舍五入方法

function round(v/*alue*/, p/*recision*/){
  p = Math.pow(10, p>>>31 ? 0 : p|0)
  v *= p
  return (v + 0.5 + (v>>31)|0) / p
}

在開波前我們先要了解一個現實,那就是雖然JS僅有Number這個數值類型,並且Number底層采用IEEE 754 64bit Double precision floating-point編碼,但JS中實際上還是存在Signed Int32、Unsigned Int32和Unsigned Int16的數值編碼方式,只是它們僅存在於運算過程中而已,而按位運算則是其中之一。

 

Bitwise Operation                      

  NOT Operation

    取反操作,符號為~, ~1=0、~0=1 。

    JS的底層實現:~ToInt32(GetValue(expr))

    由於Signed Int32采用補碼方式編碼,因此會存在對n取反后結果等於-n-1,即~n=-n-1。   

  Bitwise OR

  或操作,符號為|, 1|1=11|0=10|0=0 。

  JS的底層實現:ToInt32(GetValue(oprand1)) | ToInt32(GetValue(oprand1))

  Bitwise AND

  與操作,符號為&, 1&1=11&0=00&0=0 。

      JS的底層實現:ToInt32(GetValue(oprand1)) & ToInt32(GetValue(oprand1))

  Exclusive OR

      異或操作,符號為^, 1^1=01^0=10^0=0 。

  JS的底層實現:ToInt32(GetValue(oprand1)) ^ ToInt32(GetValue(oprand1))

 

Bitwise Shift                        

  Arithmetic Shift

  Signed Right Shift Operator

    有符號右移操作符,符號為>>。

      JS的底層實現:ToInt32(GetValue(oprand1)) >> (ToUint32(GetValue(oprand2)) & 0x1F)。

            示例:0111>>3,得到0000;1001>>3,得到1111

            注意:由於Int32采用補碼形式存儲,因此 正數>>31 得到0,而 負數>>31 得到 -1。

  Signed Left Shift Operator

    有符號左移操作符,符號為<<。

    JS的底層實現:ToInt32(GetValue(oprand1)) << (ToUint32(GetValue(oprand2)) & 0x1F)。

    示例:0111<<3,得到0000;1001<<3,得到1100

  Logical Shift

  Unsigned Right Shift Operator

    無符號右移操作符,符號為>>>。

      JS的底層實現:ToInt32(GetValue(oprand1)) >>> (ToUint32(GetValue(oprand2)) & 0x1F)。

            示例:0111>>>3,得到0000;1001>>>3,得到0001

            注意:由於Int32采用補碼形式存儲,因此 正數>>>31 得到0,而 負數>>>31 得到 1。

 

Abstract Operations                      

  [[DefaultValue]](hint)

    用於獲取對象的PrimitiveValue。具體規則如下:

      hint為string或hint為空,且對象類型為Date object時:

        1. 調用toString方法,若返回值為primitive value則直接返回;

        2. 調用valueOf方法,若返回值為primitive value則直接返回;

        3. 拋出TypeError實例。

      hint為number或hint為空,且對象類型不為Date object時:

        1. 調用valueOf方法,若返回值為primitive value則直接返回;

        2. 調用toString方法,若返回值為primitive value則直接返回;

        3. 拋出TypeError實例。

  ToPrimitive(input[, preferredType])

    用於獲取入參input的PrimitiveValue。具體規則如下:

      1. 若入參input的類型為Undefined,Null,Boolean,Number,String都直接獲取其[[PrimitiveValue]];

                 2. 其他情況則調用input的[[DefaultValue]](preferredType)方法

  ToNumber

    用於將其他數據類型轉換為Number type。具體規則如下:

      1. Undefined -> NaN

      2. Null -> +0

      3. Boolean,true -> 1

             false -> 0

      4. Object,ToNumber(ToPrimitive(arg, hint-number))

      5. String,對於無法解析為StringNumericLiteral的字符串則返回NaN

             StringNumericLiteral與NumericLiteral的區別:

            a. 前后可以有多個空格符號或LineTerminator;

              示例: Number("\r\n 123\r\r\n ") 結果為 123 

            b. 前面可以有N個0,依然以十進制來解析字符串;

              示例: Number("0123") 結果為 123,而var num = 0123則是以八進制表示83 

            c. 若指定符號,則符號后面要緊跟數值。否則會返回NaN;

              示例:

Number("-    123");                    //結果為 NaN
Number("-123");                        // 結果為-123
var nl = -       123;console.log(nl);  //結果為-123
var nl = - +      123;console.log(nl); //結果為-123

            d. 若StringNumericLiteral僅含LineTeminator和WhiteSpace,則返回0。

            LineTerminator包含 <LF>(換行符,\n,U+000A)

                      <CR>(回車符,\r,U+000D)

                      <LS>(Unicode中的行分隔符,U+2028)

                      <PS>(Unicode中的段落分隔符,U+2029)

            WhiteSpace包含 ASCII的空白字符

                      <TAB>(縮進TAB符,\t,U+0009)

                      <VT>(垂直縮進TAB符,\v,U+000B)

                      <FF>(分頁符,\f,U+000C)

                      <SP>(普通空格符,U+0020)

                      <NBSP>(非斷行空格符,U+00A0)

                     <BOM>(bit order mark,Unicode中的零寬非斷行空格,U+FEFF)

                      作用:作為UTF格式編碼的文件的首個字符,用於程序在解析該文件時猜測采用的是采用哪種UTF編碼方式。

                     <USP>(Unicode中的所有空白字符)具體看http://www.cnblogs.com/winter-cn/archive/2012/04/17/2454229.html

    

  ToInteger([value])

    具體規則如下:

function ToInteger(value){
  var num = ToNumber(value)
  if (Number.isNaN(num)) return 0
  if (num === 0 || !Number.isFinite(num)) return num
  return sign(num)*floor(abs(num))
}  

  ToInt32([value])

    具體規則如下:

function ToInteger(value){
  var num = ToNumber(value)
  if (Number.isNaN(num)) return 0
  if (num === 0 || !Number.isFinite(num)) return num

  var mod = 2<<32
  num = sign(num)*floor(abs(num))
  num = num - mod * floor(num/mod)
  if (num > 2<<31){
    num = ~(num & -1>>31>>>1) + 1
  }
  return num
}

  ToUint32([value])

    具體規則如下:

function ToInteger(value){
  var num = ToNumber(value)
  if (Number.isNaN(num)) return 0
  if (num === 0 || !Number.isFinite(num)) return num

  var mod = 2<<32
  num = sign(num)*floor(abs(num))
  num = num - mod * floor(num/mod)
  return num
}

  ToUint16([value])

    具體規則如下:

function ToInteger(value){
  var num = ToNumber(value)
  if (Number.isNaN(num)) return 0
  if (num === 0 || !Number.isFinite(num)) return num

  var mod = 2<<16
  num = sign(num)*floor(abs(num))
  num = num - mod * floor(num/mod)
  return num
}

 

Usage                            

  說了這么多還是不如直接看療效吧

//奇偶判斷
function isEven(val){
  return !(val&1)
}
function isOdd(val){
  return !!(val&1)
}

// 字符串是否含某字符判斷
function contains(str, c){
  return !!~str.indexOf(c)
}

// 正負號判斷
function isPos(val){
  return !(val>>31)
}
function isNeg(val){
  return !!val>>>31
}

// 掩碼
function getGroup(ip, mask){
  return (ip&mask).toString(2)
}

// 大小寫轉換
function toLowerCase(ll){
  return String.fromCharCode(ll.charCodeAt()|1<<5)
}
function toUpperCase(ul){
  return String.fromCharCode(ul.charCodeAt()&((-1>>>25)^(1<<5)))
}

 

Take Action                          

  回到最初四舍五入法方法,其中利用位運算的就兩個部分,  p>>>31 ? 0 : p|0 和 v + 0.5 + (v>>31)|0

  p>>>31用於判斷p的正負號,若p為正數則返回0,若p為負數則返回1;而p|0則用於截取p的整數部分。

  0.5 + v>>31實質是用於令0.5與v具有相同符號而已,v>>31若v為整數則返回0,若v為負數則返回-1。

 

Conclusion                          

  也許在日常工作中確實很少使用按位運算,大概有三個原因吧:

  1. 確實沒這個需求;

  2. 有這個需求但不會用;

  3. 有這個需求而且會用,但其他同事不懂導致可維護性“低”。

  但不管用到與否,理解個中原理還是很爽的!

  尊重原創,轉載請注明來自:http://www.cnblogs.com/fsjohnhuang/p/5142200.html^_^肥子John

 

Thanks                            

http://www.cnblogs.com/winter-cn/archive/2012/04/17/2454229.html

http://es5.github.io

http://www.cnblogs.com/silin6/p/4367019.html


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com