左移右移运算

左移右移运算,第1张

>>右移时,空出来的高位用1还是0补上是由计算机系统决定的,不过从你给的题目可看出,是由要位移的数的高位决定的,如果高位为1则用1补,高位为0则用0补,
-5用十六进制表示就是0xffff fffb,右移3位,高位空出来的3位用1补,就是0xffffffff,即-1
5用十六进制表示就是0x00000005,右移3位,高位空出来的3为用0补,就是0x00000000,即0
>>>是无符号右移运算符,即空出来的位用0补,所以
-5>>>3即0xfffffffb>>>3 得到的十六进制数为0x1fffffff 也就是536870911

java中>>代表的是向右移位运算,而>>=代表的是向右移位运算时将运算的结果同时赋值给原值,举个例子说明一下:
int index = 7;
int index2 = index>>1,则表示将7转化为二进制数后向右移动一位,而原值不变,故index = 7 ,index2 = 3;
int index3 = index>>=1,则表示将7转化为二进制数后向右移动一位并且将结果赋值给原值,故 index = 3 ,index3 = 3

之前对js的一些涉及到二进制的运算符一直似懂非懂,看到了就一脸懵逼,还得去控制台算一下。然后最近看算法的时候又看到了这个运算符,这里就简单介绍一下学习这些位运算符的过程。
注意: 以下运算均不涉及到小数。

先说这句话是什么意思。左移位是二进制的一种运算,就是在不改变二进制数值32位长度的前提下,将每位的数字都向左移动,左边移出去的直接丢弃,右边空出来的位置用0填充。无符号就是保持符号位不变,即本来是正数,移位后一样为正数。

这里以 7 << 2 为例。

首先将7转为二进制是 0000 0000 0000 0000 0000 0000 0000 0111
然后对其向左移两位

得到值为 0000 0000 0000 0000 0000 0000 0001 1100
转换为十进制为 28即 7 << 2 = 28 。

然后我们对以上的运算过程做一个处理,将这些二进制转换为我们熟悉的十进制。

对移位后的算式进行合并项可得到 2^4 + 2^3 + 2^2 = (2^2 + 2^1 + 2^0) 2^2 ,即 2^4 + 2^3 + 2^2 = (2^2 + 2^1 + 2^0) 2^2 = 7 2^2 。由此我们可得出 7 << 2 = 7 2^2 = 28 。
我们通过计算几个简单的左移位运算,与标准答案进行比较,验证一下这个结论。

在控制台中以上几个算式的结果为

答案完全一致。说明我们的结论是正确的。当然这个结论 仅限于那些二进制移位不会左移移出的数字的简单运算 。当我们遇到一些简单的可以口算的左移位运算时就可以使用这个结论快速得到结果,如果对于 99999 << 66 这种较复杂的运算你也用这个结论计算,也没有人会介意。

下面我们看一下负数的左移位运算。以 -66 << 2 为例。
首先,我们先复习一下负数如何转换为二进制。
负数转换为二进制的步骤有三:

然后对其向左移两位

得到值为 1111 1111 1111 1111 1111 1110 1111 1000 然后我们将其转换成十进制。

转换为十进制为 -264即 -66 << 2 = -264 。

刚刚我们计算 -66 的二进制得到的是 1111 1111 1111 1111 1111 1111 1011 1110 。我们在控制台验证一下我们得到的这个二进制。

我们比较一下下面几个算式。

是的没错,进行无符号左移位运算时,当两个数的绝对值相等时,其相同位数的移位的绝对值一定相等。

这里以 666 >> 3 为例。

首先将666转换为二进制是 0000 0000 0000 0000 0000 0010 1001 1010 。
然后对其向右移三位。

得到值为 0000 0000 0000 0000 0000 0000 0101 0011
转换为十进制为 83即 666 >> 3 = 83 。

然后我们对以上的运算过程做一个处理,将这些二进制转换为我们熟悉的十进制。

这个规律好像不太好总结?

这里以 -666 >> 3 为例。

因为是有符号的运算,所以这里不再适用上一小节说的js的特殊处理。先将-666转换为二进制。

即-666的二进制形式为 1111 1111 1111 1111 1111 1101 0110 0110 ,然后对其进行有符号右移位运算

移位后得到的值为 1111 1111 1111 1111 1111 1111 1010 1100 ,是一个负值,我们将其转成十进制。

我们对此结果进行验证。

可见,我们的运算是完全正确的。

这里我们以 666 >>> 3 为例。

首先将666转换为二进制是 0000 0000 0000 0000 0000 0010 1001 1010 。
然后对其向右移三位。

得到值为 0000 0000 0000 0000 0000 0000 0101 0011
转换为十进制为 83即 666 >> 3 = 83 。

这里以 -666 >> 3 为例。

因为是有符号的运算,所以这里不再适用上一小节说的js的特殊处理。先将-666转换为二进制。

即-666的二进制形式为 1111 1111 1111 1111 1111 1101 0110 0110 ,然后对其进行有符号右移位运算

移位后得到的值为 0001 1111 1111 1111 1111 1111 1010 1100 ,转成十进制为536870828。
是不是超级大。因为是无符号右移位运算,所以在左边空出部分不论正负都会填充0

我们对此结果进行验证。

可见,我们的运算是完全正确的。

注意:因为对负数进行无符号右移位运算时,所得结果很大,所以在使用过程中需要格外注意。

疑问:左移位和右移位根本都是只对位置进行了移动,那么对于 x1 >> k = y1 和 y2 << k = x2 中的 x1 等于 x2 , y1 等于 y2 吗?

不一定。因为我们不能确保移动过程中被丢弃的值均为0。但凡有一个1被丢弃,就不会相等。而如果被丢弃的都是0,那么 x1 === x2 y1 === y2 。如下图所示。

这里以 66 & 33 为例。

首先将两个数转换为二进制是 0000 0000 0000 0000 0000 0000 0100 0010 和 0000 0000 0000 0000 0000 0000 0010 0001 。
然后对其进行与运算。

得出结果为 0

负数的与运算与正数并无区别,不做讨论。

这里以 66 | 66 为例。

首先将两个数转换为二进制是 0000 0000 0000 0000 0000 0000 0100 0010 和 0000 0000 0000 0000 0000 0000 0100 0010 。
然后对其进行与运算。

得出结果为 66

负数的与运算与正数并无区别,不做讨论。

这里以 66 ^ 66 为例。

首先将两个数转换为二进制是 0000 0000 0000 0000 0000 0000 0100 0010 和 0000 0000 0000 0000 0000 0000 0100 0010 。
然后对其进行与运算。

得出结果为 0

负数的与运算与正数并无区别,不做讨论。

这里以 ~66 为例。

首先将其转换为二进制是 0000 0000 0000 0000 0000 0000 0100 0010 。
然后对其进行与运算。

将结果( 1111 1111 1111 1111 1111 1111 1011 1101 )转换为十进制

得出结果为 -67

这里我们再我看几个例子。

从中我们可以看出, 位非 *** 作就是对数字加一,然后取负 。我们可以写个简单的判断方法来验证。

位运算符运算结果非常有趣,在平时可以多加应用,但是一定要注意可能产生大数的预算,避免产生不必要的BUG。
这篇文章只是做了一个简单的介绍。后面有空了会做一下在实际开发中的应用,虽然我可能很久都遇不到。

JavaScript学习指南:JS入门教程

举个例子 int a =7>>1 , 就是将7的二进制(111)向右移动一位,就是(011)就是3,所以int a=3;
也可看成是除以二,如果左 *** 作数是奇数就看做(a-1)÷2,是偶数就是 a÷
2
右移位运算就是将左 *** 作数的比特位向右移动第二个 *** 作数指定的位数,在二进制中,首位是用来表示正负的,若右移运算符的第一个 *** 作数是正数,就填充0,是负数就填充1;

这不就是无符号右移嘛,当时第一感觉是是为了取绝对值,后来发现并不是,尝试了多次之后,发现情况有点诡异啊,我们使用 chrome 调试工具运行一下 js 中的无符号右移 0 位。

不仅是 null 无符号右移会变成 0 , js 中的其他非数值做此运算都会变成 0 。

接下来我们来看看为什么会这样(事实上不仅仅只是无符号右移是这样)。要理解这个问题需要先明白什么是位运算以及为什么需要位运算,然后搞明白 js 中的位运算有什么特别之处。

敬请期待

(这一部分我是拿 java、go 与 js 做对比的。)

这在 java、go、c 中都是不被允许的

细心的人已经发现,基本类型里并没有浮点型。

事实上在 js 中的 Number 类型是不区分 int、long、float、double 类型的( go 的用户们就呵呵一笑了,来来来,我们的浮点型就能王炸你)。回正题,不区分整型、浮点型那怎么存储呢,为了不丢失精度, js 中的 Number 类型实际上一个基于 IEEE 754 标准的双精度64位浮点数( java 的同学就把它当成 double 看)。看到这我想很多人应该能明白为什么 js 里浮点数也能参与位运算了吧。这也是没有办法,因为对于内存来说整型、浮点型都没有区别了。

这里是有一个问题的,因为当 js 需要进行位运算时,会将 *** 作数通通转成 32 位比特序列(0,1),也就是补码。 *** 作完成之后,再按照 64 位浮点数存储

直接丢弃!!! 曾呐!这么虎?

没错,就是这么暴力,那么问题来了,既然小数部分不参与位运算,那么为什么不能像 java、go 那样直接禁止呢?关于这个问题,我想那就是语言设计者的想法,我就不知道了。但是这其实也带来了一些特别的 *** 作,比如在 js 中双取反是可以做取整 *** 作的。

js 需要进行位运算的时候,对于非数值类型,会首先将 *** 作数转成一个整型(就是0)然后在进行运算。这就解释了为什么 js 中可以允许非数值类型参与运算,其实这是个伪命题,因为实质上是对非数值 *** 作数的整型表达式进行的位运算。

这里需要注意,上面说过了 js 中的整型在内存中都是一个 64 位双精度浮点型,但是 js 进行位运算时,会将 *** 作数转成带符号位的 32 位比特序列(0,1),也就是补码。运算结束后,再按照 64 位存储。那么问题来了,这里肯定会存在精度丢失对吧,这应该不难理解。 js 确实也是这样处理的,超过 32 位的部分直接截断。

所以对一个非数值变量做取反 *** 作,得到的一定是 -1,因为实际上等于对 0 做取反 *** 作。

首尾呼应一下,毕竟就是这个问题使我查资料写了这篇文章。

首先解释一下, >>> 无符号右移原本是 java 里特有的(这里是和 js、go 对比,其他语言我没用过,不能乱说)。 js 中的无符号右移跟 java 几乎一样,除了一点两种语言处理方式完全不一样。

那就是并没有真正发生移位的情况下,符号位会不会被替换成0。 java 中是不会替换的,但是 js 中是会发生替换的。

当 *** 作数是正数的时候,不管有没有真的移位并没有区别,因为正数的符号位是 0。
当 *** 作数是负数时,移动位数大于0,也体现不出区别:

但是当 *** 作数是负数,无符号右移 0 位时,区别就大了:

这是因为 -1 的补码是:

>>>0 实际上并没有发生数位变化,但是 js 却会把符号位替换成 0,

此时原来负数的补码,变为了正数的源码(这就是为什么 js 中 -1>>>0 会变成一个巨大的正整数)。

js 中无符号右移时,不管正数、负数都会首先将符号位替换成 0,然后再进行移位。也就是说,该运算符永远返回正整数。

js 的位运算,为什么会有这么多奇怪的地方呢?我相信很多同学都会有这种想法,特别是 java 的同学们吧。为此我查了 js 的历史。

1995 Sun 公司正式发布 java 语言,当时的网景公司正在为它们的 Navigator 浏览器寻找一种网页脚本(此前的浏览器不具备互动能力)。当他们看到 Sun 公司的宣传后,与 Sun 合作开发全新的脚本语言 javascript 。此前我一直不明白 js 既然不是 java 的脚本,为什么叫这个名字。现在懂了,因为当时新脚本语言的决策中, Sun 公司占了很大一环。

1995年5月 按照公司的要求(一个像 java 但是比 java 简单的脚本语言), Brendan Eich 仅用10天就写出了 javascript

在我们膜拜大神的时候,也要认清一个现实,当时给 Brendan Eich 的时间太短了,所以很多问题并没有很好的解决,而且一边模仿 java、c ,一边还要简化数据类型、内存模型。我觉得这就是为什么 js 的位运算这么奇怪的原因。

js 完全套用了 java 的位运算符。
但是 java 的位运算是针对整数的,对 js 没什么用啊,因为 js 中,所有数字都保存为双精度浮点型。如果使用它们的话, js 不得不将 *** 作数先转为整数,然后再进行运算。

所以很多人不建议在 js 中使用位运算,理由是 js 天生就会进行类型转换,使得效率降低。

首先纠正一个错误哈
>>是带符号右移
>>>才是无符号右移
带符号右移就是将那个数转为2进制然后在前面补0或1
如果是正数就补0
负数补1
例如11
>>
2,则是将数字11右移2位
计算过程:
11的二进制形式为:0000
0000
0000
0000
0000
0000
0000
1011,然后把低位的最后两个数字移出,因为该数字是正数,所以在高位补零。则得到的最终结果是0000
0000
0000
0000
0000
0000
0000
0010。转换为十进制是2。
其他的
到网上一搜就有了。。
无符号右移与带符号右移的区别就是
无符号始终补0

>>是带符号右移
>>>才是无符号右移
带符号右移就是将那个数转为2进制然后在前面补0或1
如果是正数就补0
负数补1
例如11 >> 2,则是将数字11右移2位
计算过程:
11的二进制形式为:0000 0000 0000 0000 0000 0000 0000 1011,然后把低位的最后两个数字移出,因为该数字是正数,所以在高位补零。则得到的最终结果是0000 0000 0000 0000 0000 0000 0000 0010。转换为十进制是2。
无符号右移与带符号右移的区别就是 无符号始终补0


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/13365374.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-07-22
下一篇 2023-07-22

发表评论

登录后才能评论

评论列表(0条)

保存