你可能不知道的 JavaScript 中数字取整

今天做项目中看到了这样一句JavaScript代码 let val = Math.random() * 1e9 >> 0; 就是随机了一个 [0, 1e9) 的一个整数。第一眼看到其中的 >> 有点不解,只是模糊的记得js中还有一些其它的类似的运算符。

Javascript 完全套用了 Java 的位运算符,包括按位与&、按位或|、按位异或^、按位非~、左移<<、带符号的右移>>和用0补足的右移>>>。
这套运算符针对的是整数,所以对 Javascript 完全无用,因为 Javascript 内部,所有数字都保存为双精度浮点数。如果使用它们的话,Javascript 不得不将运算数先转为整数,然后再进行运算,这样就降低了速度。而且"按位与运算符"&同"逻辑与运算符"&&,很容易混淆。

简单整理了一下:

运算符 用  法 描述
按位与(AND) a & b 对于每一个比特位,只有两个操作数相应的比特位都是 1 时,结果才为 1,否则为 0。
按位或(OR) a | b 对于每一个比特位,当两个操作数相应的比特位至少有一个 1 时,结果为 1,否则为 0。
按位异或(XOR) a ^ b 对于每一个比特位,当两个操作数相应的比特位有且只有一个 1 时,结果为 1,否则为 0。
按位非(NOT) ~a 反转操作数的比特位,即 0 变成 1,1 变成 0。
左移(Left shift) a << b 将 a 的二进制形式向左移 b (< 32) 比特位,右边用 0 填充。
有符号右移 a >> b 将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位。
无符号右移 a >>> b 将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位,并使用 0 在左侧填充。

然而通过这些位运算符是可以帮助我们实现小数转为整数的,有些位运算的操作的效率比js中其它的转换方法效率还要高,下面以其来看看JavaScript 中数字取整的方法都有哪些。

快速取整方法

~~number

双波浪线 ~~ 操作符也被称为“双按位非”操作符。你通常可以使用它作为代替 Math.trunc() 的更快的方法。失败时返回0,这可能在解决 Math.trunc() 转换错误返回 NaN 时是一个很好的替代。

但是当数字范围超出 ±2^31−1 即:2147483647 时,异常就出现了

console.log(~~47.11)  // -> 47
console.log(~~1.9999) // -> 1
console.log(~~3)      // -> 3
console.log(~~[])     // -> 0
console.log(~~NaN)    // -> 0

// 异常情况
console.log(~~2147493647.123) // -> -2147473649

number | 0

| (按位或) 对每一对比特位执行或(OR)操作。

console.log(20.15 | 0);  // -> 20
console.log((-20.15) | 0);  // -> -20

// 异常情况
console.log(3000000000.15 | 0);  // ->  -1294967296

number ^ 0

^ (按位异或),对每一对比特位执行异或(XOR)操作。

console.log(20.15 ^ 0);          // -> 20
console.log((-20.15) ^ 0);       // -> -20

// 异常情况
console.log(3000000000.15 ^ 0); // -> -1294967296

number << 0 或 number >> 0

<< (左移) 操作符会将第一个操作数向左移动指定的位数。向左被移出的位被丢弃,右侧用 0 补充。

console.log(20.15 << 0);     // -> 20
console.log((-20.15) << 0);  //-20

// 异常情况
console.log(3000000000.15 << 0);  // -> -1294967296

上面这些按位运算符方法执行很快,当你执行数百万这样的操作非常适用,速度明显优于其他方法。但是代码的可读性比较差。还有一个特别要注意的地方,处理比较大的数字时(当数字范围超出 ±2^31−1 即 ±2147483647 即 2.147483647e9),会有一些异常情况。使用的时候明确的检查输入数值的范围。

位运算符在js中的用法小结:

  1. 使用&运算符判断一个数的奇偶
// 偶数 & 1 = 0
// 奇数 & 1 = 1
console.log(2 & 1)    // 0
console.log(3 & 1)    // 1
  1. 使用~, >>, <<, >>>, |来取整
console.log(~~ 6.83)    // 6
console.log(6.83 >> 0)  // 6
console.log(6.83 << 0)  // 6
console.log(6.83 | 0)   // 6
// >>>不可对负数取整
console.log(6.83 >>> 0)   // 6
  1. 使用^来完成值交换
var a = 5
var b = 8
a ^= b
b ^= a
a ^= b
console.log(a)   // 8
console.log(b)   // 5
  1. 使用&, >>, |来完成rgb值和16进制颜色值之间的转换
/**
 * 16进制颜色值转RGB
 * @param  {String} hex 16进制颜色字符串
 * @return {String}     RGB颜色字符串
 */
  function hexToRGB(hex) {
    var hexx = hex.replace('#', '0x')
    var r = hexx >> 16
    var g = hexx >> 8 & 0xff
    var b = hexx & 0xff
    return `rgb(${r}, ${g}, ${b})`
}

/**
 * RGB颜色转16进制颜色
 * @param  {String} rgb RGB进制颜色字符串
 * @return {String}     16进制颜色字符串
 */
function RGBToHex(rgb) {
    var rgbArr = rgb.split(/[^\d]+/)
    var color = rgbArr[1]<<16 | rgbArr[2]<<8 | rgbArr[3]
    return '#'+ color.toString(16)
}
// -------------------------------------------------
hexToRGB('#ffffff')               // 'rgb(255,255,255)'
RGBToHex('rgb(255,255,255)')      // '#ffffff'

常用的直接取整方法

parseInt(string[, radix])

直接取整就是舍去小数部分。函数解析一个字符串参数,并返回一个指定基数的整数 (数学系统的基础)。这个估计是直接取整最常用的方法了。

参数 radix 是一个介于2和36之间的整数(数学系统的基础),表示上述字符串的基数。比如参数"10"表示使用我们通常使用的十进制数值系统。始终指定此参数可以消除阅读该代码时的困惑并且保证转换结果可预测。当未指定基数时,不同的实现会产生不同的结果,通常将值默认为10。

parseInt("2015nov"),  //2015
parseInt(""),  //NaN
parseInt("0xA"),  //10(十六进制)
parseInt(20.15),  //20
parseInt(-20.15),  //-20
parseInt("070");  //56(八进制数)

Math.trunc(value)

将数字的小数部分去掉,只保留整数部分。不像 Math 的其他三个方法: Math.floor()Math.ceil()Math.round()Math.trunc() 的执行逻辑很简单,仅仅是删除掉数字的小数部分和小数点,不管参数是正数还是负数。

传入该方法的参数会被隐式转换成数字类型。

Math.trunc(13.37)    // 13
Math.trunc(42.84)    // 42
Math.trunc(0.123)    //  0
Math.trunc(-0.123)   // -0
Math.trunc("-1.123") // -1
Math.trunc(NaN)      // NaN
Math.trunc("foo")    // NaN
Math.trunc()         // NaN

Math.round(value)

将数值四舍五入为最接近的整数。如果参数的小数部分大于 0.5,则舍入到下一个绝对值更大的整数。 如果参数的小数部分小于 0.5,则舍入到下一个绝对值更小的整数。如果参数的小数部分恰好等于0.5,则舍入到下一个在正无穷(+∞)方向上的整数。注意,与很多其他语言中的round()函数不同,Math.round()并不总是舍入到远离0的方向(尤其是在负数的小数部分恰好等于0.5的情况下)。

console.log(Math.round(20.1));   // -> 20
console.log(Math.round(20.5));   // -> 21
console.log(Math.round(20.9));   // -> 21
console.log(Math.round(-20.1));  // -> -20
console.log(Math.round(-20.5));  // -> -20 注意这里是-20而不是-21
console.log(Math.round(-20.9));  // -> -21

Math.floor(value)

向下取整, 返回一个表示小于或等于指定数字的最大整数的数字。

console.log(Math.floor(20.1));   // -> 20
console.log(Math.floor(20.5));   // -> 20
console.log(Math.floor(20.9));   // -> 20
console.log(Math.floor(-20.1));  // -> -21
console.log(Math.floor(-20.5));  // -> -21
console.log(Math.floor(-20.9));  // -> -21

Math.ceil(value)

向上取整,返回一个大于或等于给定数字的最小整数。

console.log(Math.ceil(20.1));   // -> 21
console.log(Math.ceil(20.5));   // -> 21
console.log(Math.ceil(20.9));   // -> 21
console.log(Math.ceil(-20.1));  // -> -20
console.log(Math.ceil(-20.5));  // -> -20
console.log(Math.ceil(-20.9));  // -> -20

参考:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math
https://juejin.im/post/5a98ea2f6fb9a028bb186f34
https://juejin.im/entry/57317b2679df540060d5d6c2
http://www.ruanyifeng.com/blog/2010/01/12_javascript_syntax_structures_you_should_not_use.html
http://www.css88.com/archives/8488

你可能感兴趣的:(你可能不知道的 JavaScript 中数字取整)