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
  • 相关阅读:
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    整理任正非思想:团结奋斗 再创华为佳绩-1994
    整理任正非思想:赴美考察散记-1994
    你不能不知道的六种 Python 图像库的图片读取方法总结
    Spring boot 2.0 Actuator 的健康检查
    Springboot启动后只能本地访问,无法通过外部IP访问
    Ironic 的 Rescue 救援模式实现流程
  • 原文地址:https://www.cnblogs.com/furuihua/p/13454237.html
Copyright © 2011-2022 走看看