首页 > 其他分享 >双重按位非运算符 ~~ 对数字取整

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

时间:2024-01-31 14:37:35浏览次数:17  
标签:数字 32 valueOf NaN 运算符 toString 取整 位非

介绍

按位非运算符(~)将操作数的位反转。它将操作数转化为 32 位的有符号整型。也就是可以对数字进行取整操作(保留整数部分,舍弃小数部分)。

~-2 // 1
~-2.222 // 1

并且按位非运算时,任何数字 x(已被转化为 32 位有符号整型) 的运算结果都是 -(x + 1)

那么双重按位非(~~)对数字的运算结果就是 -(-(x + 1) + 1),结果就是 x

所以利用 ~~ 操作数字时就可对其进行取整操作(右移操作符 x >> 0 和按位或操作符 x | 0 也有相同作用)。

如果操作的不是 Number 类型的,操作的对象会先转化 Number 类型,下面一起来看看。

操作原始数据类型时

~~(-2.999);  // => -2
~~null; // => 0
~~undefined; // => 0
~~0;         // => 0
~~(1/0);     // => 0
~~false;     // => 0
~~true;      // => 1
~~'1234'     // => 1234
~~'1234asdf' // => 0
~~NaN        // => 0

~~ 对于不能转化为数字的数据(NaN) ,操作的结果为 0

右移操作符 >> 和按位或操作符 | 也是如此。

(-2.999) >> 0   // => -2
null >> 0       // => 0
undefined >> 0  // => 0
0 >> 0          // => 0
(1/0) >> 0      // => 0
false >> 0      // => 0
true >> 0       // => 1
'1234' >> 0     // => 1234
'1234asdf' >> 0 // => 0
NaN >> 0        // => 0

(-2.999) | 0   // => -2
null | 0       // => 0
undefined | 0  // => 0
0 | 0          // => 0
(1/0) | 0      // => 0
false | 0      // => 0
true | 0       // => 1
'1234' | 0     // => 1234
'1234asdf' | 0 // => 0
NaN | 0        // => 0

操作对象数据类型时

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

详细的转换过程:

  1. 调用对象的valueOf方法
    如果该方法返回一个原始值,JavaScript会尝试将这个原始值转换为一个数字。如果valueOf方法返回的还是一个对象,JavaScript会继续调用对象的toString方法。

  2. 调用对象的toString方法
    这个方法返回一个字符串,然后JavaScript会尝试将这个字符串转换为一个数字。

  3. 转换字符串为数字
    一旦从valueOftoString方法中获得了一个原始值(通常是字符串),JavaScript会按照字符串到数字的转换规则来处理这个值。

如果valueOftoString返回的值是NaN,那么结果就是NaN。如果字符串不能被解析为一个有效的数字,结果也是NaN~~ 操作 NaN 返回 0

所以也就有下面的结果:

~~{};    // => 0
~~{a:1}  // => 0
~~[];    // => 0
~~[1];   // => 1
~~[1,2]; // => 0

对于数组而言,将数组转化为数字,会调用数组的 toString()

数组的 toString 方法实际上在内部调用了 join() 方法来拼接数组并返回一个包含所有数组元素的字符串,元素之间用逗号分隔。如果 join 方法不可用或者不是函数,则会使用 Object.prototype.toString 来代替,并返回 [object Array]

上面的 [1,2] 经过 toString() 后是 '1,2' , 转为数字则是 NaN。所以 ~~[1,2] 结果为 0。

下面是对象有自定义的 valueOf() 或者 toString() 情况

var a = {
    valueOf:function(){return '11'},  // 字符串'11' 可被转化为 数字 11
    toString:function(){return 12}
}
~~a // => 11

var b = {
    valueOf:function(){return 'asdf'}, // 字符串'asdf' 转化为 NaN
    toString:function(){return 12}
}
~~b // => 0

var c = {
    toString:function(){return 12} // 没有 valueOf() ,则调用 toString()
}
~~c // => 12

var d = {
    toString:function(){return 'asdf'} // 字符串'asdf' 转化为 NaN
}
~~d // => 0

可进行运算的数字的有效范围

由于 按位运算总是将操作数转换为 32 位整数。 超过 32 位的数字将丢弃其最高有效位。如下例子中(来自MDN),超过 32 位的整数将转换为 32 位整数:

Before: 11100110111110100000000000000110000000000001
After:              10100000000000000110000000000001

再比如 ~~ 操作日期类型数据,DatevalueOf 方法返回以数值格式表示的一个 Date 对象的原始值,从 1970 年 1 月 1 日 0 时 0 分 0 秒到该日期对象所代表时间的毫秒数。

返回的毫秒数是超过 32 位的整数,不在 ~~ 操作的有效范围内,结果就不会是期望的那样。

var date = new Date()
Number(date) // 1706671595364
~~date // 1569578852  结果失真

所以只有对 32位浮点数(经测试,有效范围为:[-2^31,2^31-1],即[-2147483648,2147483647]) 进行按位运算时才会得到期望的结果。

~~2147483647.1 // => 2147483647  正确
~~2147483648.1 // => -2147483648  不正确

~~-2147483648.1 // => -2147483648  正确
~~-2147483649.1 // => 2147483647 不正确

需要注意的是,如果整数部分和小数部分数字之和超过了 16 位(不包括小数点),那么双重按位非操作符的结果也会不正确:( Number编码的精度 的算术会受到舍入的影响。)

image

image

使用场景

对一些函数入参校验及处理方面有用,比如传入的可能是任意数字,需要排除掉极端情况(NaNInfinity),然后取整;

function fn(){
    var param = arguments[1]
    if(param === 'number' && !isNaN(foo) && foo !== Infinity){
        var value = Number(param) || 0;
        value = (value < 0)
             ? Math.ceil(value)
             : Math.floor(value);
    }
}

使用 ~~ 后:

function fn(){
    var value = ~~arguments[1]
}

拓展

左移(<<)和右移(>>)运算符可以用来进行快速的二进制乘法和除法操作。左移一个数值实际上等于将这个数值乘以2的某个幂,而右移一个数值则等于将这个数值除以2的某个幂(忽略余数)。

但这个使用场景只适用对 2 的乘法除法操作(。。。)

let result = 6 * 2; // 结果是12

let base = 6; // 二进制表示为 110 
let shift = 1; // 左移1位 
let result = base << shift; // 结果是12,二进制表示为 1100
let result = 12 / 2; // 结果是6,但可能得到一个浮点数 let roundedResult = Math.floor(12 / 2); // 结果是6,确保得到整数

let base = 12; // 二进制表示为 1100 
let shift = 1; // 右移1位 
let result = base >> shift; // 结果是6,二进制表示为 110

总结

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

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

折腾完毕

标签:数字,32,valueOf,NaN,运算符,toString,取整,位非
From: https://www.cnblogs.com/zsxblog/p/17999178

相关文章

  • 从C向C++——运算符重载
    本文的主要知识点是C++中的运算符重载。1.运算符重载所谓重载,就是赋予新的含义。函数重载(FunctionOverloading)可以让一个函数名有多种功能,在不同情况下进行不同的操作。**运算符重载(OperatorOverloading)**也是一个道理,同一个运算符可以有不同的功能。实际上,我们已经在不知不觉中......
  • C#中问号(?)运算符的历代新增用法
    本文是B站up主十月的寒流的相应视频的学习笔记,可以直接访问该视频获取更详细的讲解:C#中问号(?)运算符的历代新增用法C#1.0三目运算符stringres=x>5?"goood":"no"C#2.0可为空的值类型(NullableValueTypes)int?x=null;上述代码本质上是Nullable<int>x=nul......
  • OpenHarmony—仅允许在表达式中使用typeof运算符
    规则:arkts-no-type-query级别:错误ArkTS仅支持在表达式中使用typeof运算符,不允许使用typeof作为类型。TypeScriptletn1=42;lets1='foo';console.log(typeofn1);//'number'console.log(typeofs1);//'string'letn2:typeofn1lets2:typeofs1ArkTS......
  • 无涯教程-Swift - 运算符
    运算符是一个符号,告诉编译器执行特定的数学或逻辑操作,Swift包含丰富的内置运算符,并提供以下类型的运算符-算术运算符比较运算符逻辑运算符按位运算符范围运算符其它运算符本教程将逐一说明算术,关系,逻辑,按位,赋值和其他运算符。算术运算符下表显示了Swift4语言支持的所......
  • (20)Powershell中的特殊运算符
    (20)Powershell中的特殊运算符Powershell中除了常见的算术运算符,赋值运算符,比较运算符,位运算符,逻辑运算符,字符串的拆分和合并运算符,转义字符,还有一些特殊的运算符,这些特殊的运算符往往可以解决一些特殊的问题,比如执行运行命令,更改值的数据类型等。1.&(调用运算符)使用&(......
  • MySQL 运算符
    本章节我们主要介绍MySQL的运算符及运算符的优先级。MySQL主要有以下几种运算符:算术运算符MySQL支持的算术运算符包括:运算符 作用加法– 减法乘法/或DIV 除法%或MOD 取余在除法运算和模运算中,如果除数为0,将是非法除数,返回结果为NULL。1、加mysql>select......
  • 被Null条件运算符摆了一道
    C#6.0引入了Null条件运算符:obj?.Invoke();等价于if(obj!=null)obj.Invoke();即遇到null时短路。于是,我不假思索地写下了这样的代码:①awaitobj?.InvokeAsync();遇到null时短路嘛,我以为是等价于这样:②if(obj!=null)awaitobj.InvokeAsync(); 于是乎,......
  • 运算符的关系简览
     C/C++运算符优先级 优先级运算符名称或含义使用形式结合方向说明1[]数组下标数组名[常量表达式]左到右--()圆括号(表达式)/函数名(形参表)--.成员选择(对象)对象.成员名--->成员选择(指针)对象指......
  • C++教程——初识C++(运算符)
    前一节我们初步认识出c++是一门面向对象的程序设计语言,并且我们初步认识了变量,数据类型,常量,输入输出以及合法的标识符命名规则,接下来我们要学习的是运算符。在c++中有逻辑运算符,赋值运算符,算数运算符,位运算符,关系运算符和杂项运算符。先从简单的算数运算符开始讲起,算数运算符简单来......
  • JS中的扩展运算符(...)和剩余运算符(...)
    一、概念在JS中,扩展运算符(spread)是三个点 (...) ,剩余运算符(rest)也是三个点 (...)二、扩展运算符(1)基本使用:扩展运算符的主要作用是将一个数组转为用逗号分隔的参数序列,它好比rest的逆运算//传递数据代替多个字符串的形式functiontest(a,b,c){console.log(a);//1......