zoukankan      html  css  js  c++  java
  • 深入理解JavaScript位运算符

     

    在开始聊位运算符之前,我们需要先来聊一聊二进制,因为位运算与二进制是密不可分的。

    二进制

    所谓的二进制,其实简单点理解就是以32位数值来表示一串十进制数值的方式吧。因为我们现在程序里面用到的都是十进制数值,但是计算机内部计算会把十进制转换成二进制再进行计算。

    我们都知道,整数有两种类型的,既:正数、负数。其实在二进制里面,它认为整数有两种类型,既有符号整数(也就是刚刚说的正数和负数)和无符号整数(其实就是正数,没有写+号罢了)。那么,二进制是如何表示一个十进制的数值呢?

    我们刚刚说过,二进制是有32位数值来表示一个十进制数值的。其实有符号整数是使用31位数值来表示整数的数值,用第32位来表示整数的符号,0表示为正数,1表示为负数。而数值范围是从-2147483648到2147483647。

    在存储数值的时候,是以两种不同方式来存储二进制形式的有符号整数:一种是存储正数、一种是存储负数。正数是以真二进制形式存储的,前31位中的每一位都表示2的幂,从第1位(位0)开始,表示 2的0次幂,第2位(位1)表示 2的1次幂,依次类推...。没用到的位用0填充,即忽略不计。

    在网上找了张图,可以帮助大家理解一下

    吴佳前端博客-位运算符1

    从图中可以看到,开始位是在右边开始的,末位是在左边,所以这点是要注意的地方。
    上图中是以数值18的二进制来做的示例,其有效位是前五位,即10010。我们用代码做个转换其实就可以看到有效位

    let num = 18
    console.log(num.toString(2)) // 10010

    那么我们如何把二进制转换成十进制的呢?上面已经说了计算方法,下面我们来用代码做个换算,更深入的理解一下

    // 18的二进制表示 10010,即我们从二进制右边到左边的幂次计算是这样的
    (Math.pow(2, 4) * 1) + (Math.pow(2, 3) * 0) + (Math.pow(2, 2) * 0) + (Math.pow(2, 1) * 1) + (Math.pow(2, 0) * 0) = 18

    根据上方的示例,我们就很清楚的看到了,从右至左分别从数值10010右边第一位进行按2的幂次相乘分别相加,最后得出十进制数值。http://blog.cuteur.cn/

    顺便我们再看一张我从网络上找的图,来加强理解吧

    吴佳前端博客-位运算符1

    其实负数也存储为二进制代码,不过采用的形式是二进制补码。计算数字二进制补码有三个步骤:

    • 确定该数字的非负版本的二进制表示(例如,要计算-18的二进制补码,首先要确定18的二进制表示)
    • 求得二进制反码,即需把 0 替换为 1,把 1 替换为 0
    • 在二进制反码上加 1

    到这,我们就讲完了二进制相关的东西了,下面我们就开始讲讲位运算符。

    位运算符

    按位非(NOT):~

    它的运算是取数值二进制的反码,然后将反码二进制数转成浮点数。反码的意思就是将二进制0和1的数值反转,上面已经说过了。

    例如:

    let num = 5 // 5的二进制 00000000000000000000000000000101
    let num1 = ~5 // 取5的二进制反码 11111111111111111111111111111010
    console.log(num1) // 最后得出 -6

    换种方式理解,其实按位非NOT:~ 实际上是在给数值求负,然后减一。其实用下面这种方式同样可以得出以上结果

    let num = 5
    let num1 = -num - 1
    console.log(num1) // 得出 -6

    按位与(AND): &

    它的运算规则是将两个操作数(二进制形式)的每一位对齐,跟据以下规则进行计算。

    • 只有同是1的时候,结果才是1
    • 其它任何情况都是0

    例如:

    
    let num = 5 & 10
    
    /*
      5的二进制:00000000000000000000000000000101
      10的二进制:00000000000000000000000000001010
      -------------------------------------------
      运算得出:00000000000000000000000000000000
    */
    // 最后将运算得出的二进制转为十进制,即按照上方说的计算方式计算
    console.log(num) // 0

    按位或(OR): |

    同样它的运算规则也是将两个操作数(二进制形式)的每一位对齐,然后跟据以下规则进行计算。

    • 只有同是0的时候,结果才是0;
    • 其它任何情况都是1;

    例如:

    
    let num = 5 | 10
    
    /*
      5的二进制:00000000000000000000000000000101
      10的二进制:00000000000000000000000000001010
      -------------------------------------------
      运算得出:00000000000000000000000000001111
    */
    // 最后将运算得出的二进制转为十进制,即按照上方说的计算方式计算
    Math.pow(2, 3) * 1 + Math.pow(2, 2) * 1 + Math.pow(2, 1) * 1 + Math.pow(2, 0) * 1
    console.log(num) // 最后得出 15

    按位异或(XOR): ^

    它的运算规则同样是将两个操作数(二进制形式)的每一位对齐,然后跟据以下规则进行计算。

    • 两位同是0或1的时候,结果才是0;
    • 其它任何情况都是1;

    例如:

    
    let num = 5 ^ 4
    
    /*
      5的二进制:00000000000000000000000000000101
      4的二进制:00000000000000000000000000000100
      -------------------------------------------
      运算得出:  00000000000000000000000000000001
    */
    // 最后将运算得出的二进制转为十进制,即按照上方说的计算方式计算
    Math.pow(2, 0) * 1
    console.log(num) // 得出 1

    左移:<<

    这个操作符会将数值的所有位向左移动指定的位数。右边空出来的位置,补0;

    例如:

    let num = 5 << 4
    
    /*
      5 的二进制:   00000000000000000000000000000101
      ---------------------------------------------
      向左移动四位: 00000000000000000000000001010000  
    */
    // 最后将移动得出的二进制转为十进制,即按照上方说的计算方式计算
    Math.pow(2, 6) * 1 + Math.pow(2, 5) * 0 + Math.pow(2, 4) * 1
    console.log(num) // 得出 80

    有符号右移:>>

    这个操作符会将数值向右移动,但保留符号位(即正负号标记)。有符号的右移操作与左移操作恰好相反。

    例如:

    let num = 8 >> 3
    
    /*
      8 的二进制:   00000000000000000000000000001000
      ---------------------------------------------
      向右移动三位: 0 0000000000000000000000000000001 
      第32位保持不动,从第31位开始往后推3位
    */
    // 最后将移动得出的二进制转为十进制,即按照上方说的计算方式计算
    Math.pow(2, 0) * 1
    console.log(num) // 得出 1

    无符号右移:>>>

    这个操作符会将数值的所有32位都向右移动,对正数来说,无符号右移的结果与有符号右移相同。
    但是对负数来说,就不一样了。首先,无符号右移是以0来填充空位,而不是像有符号右移那样以符号位之前的值来填充空位。所以,对正数的无符号右移与有符号右移结果相同,但对负数的结果就不一样了。
    其次,无符号右移操作符会把负数的二进制码当成正数的二进制码。
    而且,由于负数以其绝对值的二进制补码形式表示,因此就会导致无符号右移后的结果非常大。

    例如:

    let num = -14 >>> 2
    
    /*
      -14 的二进制:11111111111111111111111111110010
      ---------------------------------------------
      向右移动2位: 00111111111111111111111111111100
    */
    // 最后将移动得出的二进制转为十进制,即按照上方说的计算方式计算
    // 由于数值过多,计算过长。所以这里我就直接用函数递归方式计算
     function sum(n = 29, a = 0) {
        if (n > 1) {
           let d = Math.pow(2, n) * 1
           a = a + d
          return sum(--n, a)
        }
        return a
     }
     sum() // 1073741820
     console.log(num) // 得出 1073741820
  • 相关阅读:
    hdu 2222 Keywords Search
    Meet and Greet
    hdu 4673
    hdu 4768
    hdu 4747 Mex
    uva 1513 Movie collection
    uva 12299 RMQ with Shifts
    uva 11732 strcmp() Anyone?
    uva 1401
    hdu 1251 统计难题
  • 原文地址:https://www.cnblogs.com/furuihua/p/13454237.html
Copyright © 2011-2022 走看看