js中的位运算

JavaScript019

js中的位运算,第1张

在了解位运算之前, 必须先了解一下什么是原码, 反码和补码, 以及二进制与十进制的转换.

原码

一个数在计算机中是以二进制的形式存在的, 其中第一位存放符号, 正数为0, 负数为1. 原码就是用第一位存放符号的二进制数值. 例如2的原码为00000010, -2的原码为10000010

反码

正数的反码是它本身, 负数的反码是在其原码的基础上, 符号位不变, 其余各位取反.

可见如果一个反码表示的是负数, 并不能直观的看出它的数值, 通常要将其转换成原码再计算

补码

正数的补码是它本身, 负数的补码是在其原码基础上, 符号位不变, 其余各位取反, 最后+1. (即负数的补码为在其反码的基础上+1)

可见对于负数, 补码的表示方式也是让人无法直观的看出其数值的, 通常也需要转换成原码再计算.

正整数十进制转二进制

正整数的十进制转二进制的方法为将一个十进制数除以2, 得到的商再除以2, 以此类推知道商为1或0时为止, 倒序取得除得的余数, 即为转换所得的二进制数.

负整数十进制转二进制

负整数的十进制转二进制, 先将该负整数对应的正整数转为二进制, 然后对其取反再+1. 即补码的形式

十进制小数转二进制

十进制小数转二进制的方法为"乘2取整", 对十进制的小数部分乘2, 得到的整数部分即是相应的二进制码数, 然后继续对得到的小数部分乘2, 如此不断重复, 直到小数部分为0或达到精度要求为止. 顺序取得每次的整数部分, 即是该十进制小数的二进制表示.

按位运算符有6个

&: 按位与

|: 按位或

^: 按位异或

~: 按位取反

>>: 右移

<<: 左移

将运算数以二进制表示, 对应位都为1, 则结果为1, 否则为0.

使用场景示例:

判断一个数是奇数还是偶数

奇数的二进制码的最后一位数肯定是1, 而1只有最后一位为1, 按位与运算后, 结果肯定只有最后一位数是1. 而偶数的二进制表示的最后一位数是0, 和1进行按位与运算, 结果的所有位都是0.

将运算数以二进制表示, 对应位有一个为1, 则结果为1, 否则为0.

使用场景示例:

对浮点数向下求整

其实浮点数是不支持位运算的, 所以会先把小数位丢弃, 然后以整数进行位运算, 而任何数与0进行按位或操作, 结果都是它本身, 就好像是对浮点数向下求整.

将运算数以二进制表示, 对应位相同为0, 相异为1.

异或满足交换律和结合律, 数字与它本身进行异或操作, 得到0数字与0进行异或操作, 得到它本身.

使用场景示例:

交换两个变量数字的值

将操作数转换为二进制数, 然后按位求反.

浮点数是不支持位运算的,所以会先直接去除小数部分,转成整数再进行位运算,就好像是对浮点数向下求整.

~~可以进行类型转换,位运算会默认将非数字类型转换成数字类型再进行运算 (转换结果为整数 直接去除小数部分)

使用场景示例:

类型转换

移位运算符将操作数转换成二进制, 然后向左或向右移动, 超过的位丢弃, 空出的位补0.

使用场景示例:

类型转换

任何小数 把它 >>0可以取整

如3.14159 >>0 = 3

其默认将非数字类型的转换为数字类型再做运算的性质与 ~~ , | 0 一样

这不就是无符号右移嘛,当时第一感觉是是为了取绝对值,后来发现并不是,尝试了多次之后,发现情况有点诡异啊,我们使用 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 天生就会进行类型转换,使得效率降低。

js中位运算2<lt32结果不正确解决:

-----解决方案--------------------

javascript位操作符会把数值转换成32位的整数再执行操作,最后再把结果转回64位。所以结果只能是32位整数能表示的范围了。

------解决方案--------------------

肯定溢出了,如果真想得到数据,那就要想其他的办法了,比如说可以把它分解成两个小一点的数,把这两个小一点的数转为数组,循环这两个数组,相乘,得到个位,十位,百位,千位。。。。直到最高位。分别把个位,十位,百位放在另外一个数组里。输出就OK了。具体可以参考大数相乘的算法。