翼度科技»论坛 编程开发 JavaScript 查看内容

双重按位非运算符 ~~ 对数字取整

9

主题

9

帖子

27

积分

新手上路

Rank: 1

积分
27
介绍

按位非运算符(~)将操作数的位反转。它将操作数转化为 32 位的有符号整型。也就是可以对数字进行取整操作(保留整数部分,舍弃小数部分)。
  1. ~-2 // 1
  2. ~-2.222 // 1
复制代码
并且按位非运算时,任何数字 x(已被转化为 32 位有符号整型) 的运算结果都是 -(x + 1) 。
那么双重按位非(~~)对数字的运算结果就是 -(-(x + 1) + 1),结果就是 x 。
所以利用 ~~ 操作数字时就可对其进行取整操作(右移操作符 x >> 0 和按位或操作符 x | 0 也有相同作用)。
如果操作的不是 Number 类型的,操作的对象会先转化 Number 类型,下面一起来看看。
操作原始数据类型时
  1. ~~(-2.999);  // => -2
  2. ~~null; // => 0
  3. ~~undefined; // => 0
  4. ~~0;         // => 0
  5. ~~(1/0);     // => 0
  6. ~~false;     // => 0
  7. ~~true;      // => 1
  8. ~~'1234'     // => 1234
  9. ~~'1234asdf' // => 0
  10. ~~NaN        // => 0
复制代码
~~ 对于不能转化为数字的数据(NaN) ,操作的结果为 0
右移操作符 >> 和按位或操作符 | 也是如此。
  1. (-2.999) >> 0   // => -2
  2. null >> 0       // => 0
  3. undefined >> 0  // => 0
  4. 0 >> 0          // => 0
  5. (1/0) >> 0      // => 0
  6. false >> 0      // => 0
  7. true >> 0       // => 1
  8. '1234' >> 0     // => 1234
  9. '1234asdf' >> 0 // => 0
  10. NaN >> 0        // => 0
  11. (-2.999) | 0   // => -2
  12. null | 0       // => 0
  13. undefined | 0  // => 0
  14. 0 | 0          // => 0
  15. (1/0) | 0      // => 0
  16. false | 0      // => 0
  17. true | 0       // => 1
  18. '1234' | 0     // => 1234
  19. '1234asdf' | 0 // => 0
  20. NaN | 0        // => 0
复制代码
操作对象数据类型时

当 ~~ 作用于对象类型时,对象类型会先隐式转化为数字,转化的结果取决于对象的 valueOf 方法和 toString 方法返回的结果。如果对象类型转化后最终的结果是 NaN,那么 ~~ 操作 NaN 则会直接返回 0。
详细的转换过程:

  • 调用对象的valueOf方法
    如果该方法返回一个原始值,JavaScript会尝试将这个原始值转换为一个数字。如果valueOf方法返回的还是一个对象,JavaScript会继续调用对象的toString方法。
  • 调用对象的toString方法
    这个方法返回一个字符串,然后JavaScript会尝试将这个字符串转换为一个数字。
  • 转换字符串为数字
    一旦从valueOf或toString方法中获得了一个原始值(通常是字符串),JavaScript会按照字符串到数字的转换规则来处理这个值。
如果valueOf或toString返回的值是NaN,那么结果就是NaN。如果字符串不能被解析为一个有效的数字,结果也是NaN。 ~~ 操作 NaN 返回 0。
所以也就有下面的结果:
  1. ~~{};    // => 0
  2. ~~{a:1}  // => 0
  3. ~~[];    // => 0
  4. ~~[1];   // => 1
  5. ~~[1,2]; // => 0
复制代码
对于数组而言,将数组转化为数字,会调用数组的 toString()。
数组的 toString 方法实际上在内部调用了 join() 方法来拼接数组并返回一个包含所有数组元素的字符串,元素之间用逗号分隔。如果 join 方法不可用或者不是函数,则会使用 Object.prototype.toString 来代替,并返回 [object Array]。
上面的 [1,2] 经过 toString() 后是 '1,2' , 转为数字则是 NaN。所以 ~~[1,2] 结果为 0。
下面是对象有自定义的 valueOf() 或者 toString() 情况
  1. var a = {
  2.     valueOf:function(){return '11'},  // 字符串'11' 可被转化为 数字 11
  3.     toString:function(){return 12}
  4. }
  5. ~~a // => 11
  6. var b = {
  7.     valueOf:function(){return 'asdf'}, // 字符串'asdf' 转化为 NaN
  8.     toString:function(){return 12}
  9. }
  10. ~~b // => 0
  11. var c = {
  12.     toString:function(){return 12} // 没有 valueOf() ,则调用 toString()
  13. }
  14. ~~c // => 12
  15. var d = {
  16.     toString:function(){return 'asdf'} // 字符串'asdf' 转化为 NaN
  17. }
  18. ~~d // => 0
复制代码
可进行运算的数字的有效范围

由于 按位运算总是将操作数转换为 32 位整数。 超过 32 位的数字将丢弃其最高有效位。如下例子中(来自MDN),超过 32 位的整数将转换为 32 位整数:
  1. Before: 11100110111110100000000000000110000000000001
  2. After:              10100000000000000110000000000001
复制代码
再比如 ~~ 操作日期类型数据,Date 的 valueOf 方法返回以数值格式表示的一个 Date 对象的原始值,从 1970 年 1 月 1 日 0 时 0 分 0 秒到该日期对象所代表时间的毫秒数。
返回的毫秒数是超过 32 位的整数,不在 ~~ 操作的有效范围内,结果就不会是期望的那样。
  1. var date = new Date()
  2. Number(date) // 1706671595364
  3. ~~date // 1569578852  结果失真
复制代码
所以只有对 32位浮点数(经测试,有效范围为:[-2^31,2^31-1],即[-2147483648,2147483647]) 进行按位运算时才会得到期望的结果。
  1. ~~2147483647.1 // => 2147483647  正确
  2. ~~2147483648.1 // => -2147483648  不正确
  3. ~~-2147483648.1 // => -2147483648  正确
  4. ~~-2147483649.1 // => 2147483647 不正确
复制代码
需要注意的是,如果整数部分和小数部分数字之和超过了 16 位(不包括小数点),那么双重按位非操作符的结果也会不正确:( Number编码的精度 的算术会受到舍入的影响。)


使用场景

对一些函数入参校验及处理方面有用,比如传入的可能是任意数字,需要排除掉极端情况(NaN,Infinity),然后取整;
  1. function fn(){
  2.     var param = arguments[1]
  3.     if(param === 'number' && !isNaN(foo) && foo !== Infinity){
  4.         var value = Number(param) || 0;
  5.         value = (value < 0)
  6.              ? Math.ceil(value)
  7.              : Math.floor(value);
  8.     }
  9. }
复制代码
使用 ~~ 后:
  1. function fn(){
  2.     var value = ~~arguments[1]
  3. }
复制代码
拓展

左移()运算符可以用来进行快速的二进制乘法和除法操作。左移一个数值实际上等于将这个数值乘以2的某个幂,而右移一个数值则等于将这个数值除以2的某个幂(忽略余数)。
但这个使用场景只适用对 2 的乘法除法操作(。。。)
  1. let result = 6 * 2; // 结果是12
  2. let base = 6; // 二进制表示为 110
  3. let shift = 1; // 左移1位
  4. let result = base << shift; // 结果是12,二进制表示为 1100
复制代码
总结

本文探讨了使用双重按位非运算符 ~~ 对操作数取整的原理。

  • ~~ 之所以可以用来取整,是因为按位运算操作数转化为 32 位的有符号整型,会舍弃掉小数部分。并且按位非运算(~)时,任何数字 x(已被转化为 32 位有符号整型) 的运算结果都是 -(x + 1) 。那么双重按位非(~~)对数字的运算结果就是 -(-(x + 1) + 1),结果就是 x 。
  • 操作数是数字并且在位运算的有效范围内([-2^31,2^31-1]),~~ 取整才会得到期望的结果。
  • 使用场景方面对一些函数入参校验及处理方面可能有用

折腾完毕
来源:https://www.cnblogs.com/zsxblog/p/17999178
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具