<p>本文转载自:<a href="https://segmentfault.com/a/1190000008786789" title="全面解读Math对象及位运算">全面解读Math对象及位运算</a></p>
<p>Math方法和位运算几乎是被忽略得最严重的知识点, 和正则一样, 不用不知道, 一用到处查. 为了告别这种低效的编程模式, 我特地总结此篇, 系统梳理了这两个知识点. 以此为册, 助你攻破它们.</p>
<p>原文: <a href="http://louiszhai.github.io/2016/07/01/Math/">全面解读Math对象及位运算</a></p>
<h3>导读</h3>
<p>截至ES6, JavaScript 中内置(build-in)构造器/对象共有19个, 其中14个是构造器(Number,Boolean, String, Object, Function, Array, RegExp, Error, Date, Set, WeakSet, Map, Proxy, Promise), Global 不能直接访问, Arguments仅在函数调用时由JS引擎创建, 而 Math, JSON, Reflect 是以对象形式存在的, 本篇将带你走进 JS 内置对象-Math以及与之息息相关的位运算, 一探究竟.</p>
<h3>为什么Math这么设计</h3>
<p>众所周知, 如果需要使用js进行一些常规的数学运算, 是一件十分麻烦的事情. 为了解决这个问题, ECMAScript 在1.1版本中便引入了 Math. Math 之所以被设计成一个对象, 而不是构造器, 是因为对象中的方法或属性可以作为静态方法或常量直接被调用, 方便使用, 同时, Math 也没有创建实例的必要.</p>
<h3>Math中的属性</h3>
<table>
<thead><tr>
<th align="center">属性名</th>
<th align="center">描述</th>
<th align="center">值</th>
</tr></thead>
<tbody>
<tr>
<td align="center">Math.E</td>
<td align="center">欧拉常数,也是自然对数的底数</td>
<td align="center">约2.718</td>
</tr>
<tr>
<td align="center">Math.LN2</td>
<td align="center">2的自然对数</td>
<td align="center">约0.693</td>
</tr>
<tr>
<td align="center">Math.LN10</td>
<td align="center">10的自然对数</td>
<td align="center">约2.303</td>
</tr>
<tr>
<td align="center">Math.LOG2E</td>
<td align="center">以2为底E的对数</td>
<td align="center">约1.443</td>
</tr>
<tr>
<td align="center">Math.LOG10E</td>
<td align="center">以10为底E的对数</td>
<td align="center">约0.434</td>
</tr>
<tr>
<td align="center">Math.PI</td>
<td align="center">圆周率</td>
<td align="center">约3.14</td>
</tr>
<tr>
<td align="center">Math.SQRT1_2</td>
<td align="center">1/2的平方根</td>
<td align="center">约0.707</td>
</tr>
<tr>
<td align="center">Math.SQRT2</td>
<td align="center">2的平方根</td>
<td align="center">约1.414</td>
</tr>
</tbody>
</table>
<h3>Math中的方法</h3>
<p>Math对象本就有很多用于运算的方法, 值得关注的是, ES6 规范又对Math对象做了一些扩展, 增加了一系列便捷的方法. 而这些方法大致可以分为以下三类.</p>
<h3>三角函数</h3>
<table>
<thead><tr>
<th align="center">方法名</th>
<th align="center">描述</th>
</tr></thead>
<tbody>
<tr>
<td align="center">Math.sin(x)</td>
<td align="center">返回x的正弦值</td>
</tr>
<tr>
<td align="center">Math.sinh(x) ES6新增</td>
<td align="center">返回x的双曲正弦值</td>
</tr>
<tr>
<td align="center">Math.cos(x)</td>
<td align="center">返回x的余弦值</td>
</tr>
<tr>
<td align="center">Math.cosh(x) ES6新增</td>
<td align="center">返回x的双曲余弦值</td>
</tr>
<tr>
<td align="center">Math.tan(x)</td>
<td align="center">返回x的正切值</td>
</tr>
<tr>
<td align="center">Math.tanh(x) ES6新增</td>
<td align="center">返回x的双曲正切值</td>
</tr>
<tr>
<td align="center">Math.asin(x)</td>
<td align="center">返回x的反正弦值</td>
</tr>
<tr>
<td align="center">Math.asinh(x) ES6新增</td>
<td align="center">返回x的反双曲正弦值</td>
</tr>
<tr>
<td align="center">Math.acos(x)</td>
<td align="center">返回x的反余弦值</td>
</tr>
<tr>
<td align="center">Math.atan(x)</td>
<td align="center">返回x的反正切值</td>
</tr>
<tr>
<td align="center">Math.atan2(x, y)</td>
<td align="center">返回 y/x 的反正切值</td>
</tr>
<tr>
<td align="center">Math.atanh(x) ES6新增</td>
<td align="center">返回 x 的反双曲正切值</td>
</tr>
</tbody>
</table>
<h3>数学运算方法</h3>
<table>
<thead><tr>
<th align="center">方法名</th>
<th align="center">描述</th>
<th align="center">例子</th>
</tr></thead>
<tbody>
<tr>
<td align="center">Math.sqrt(x)</td>
<td align="center">返回x的平方根</td>
<td align="center">Math.sqrt(9);//3</td>
</tr>
<tr>
<td align="center">Math.exp(x)</td>
<td align="center">返回欧拉常数(e)的x次幂</td>
<td align="center">Math.exp(1);//2.718</td>
</tr>
<tr>
<td align="center">Math.pow(x,y)</td>
<td align="center">返回x的y次幂, 如果y未初始化, 则返回x</td>
<td align="center">Math.pow(2, 3);//8</td>
</tr>
<tr>
<td align="center">Math.expm1(x) ES6新增</td>
<td align="center">返回欧拉常数(e)的x次幂减去1的值</td>
<td align="center">Math.exp(1);//1.718</td>
</tr>
<tr>
<td align="center">Math.log(x)</td>
<td align="center">返回x的自然对数</td>
<td align="center">Math.log(1);//0</td>
</tr>
<tr>
<td align="center">Math.log1p(x) ES6新增</td>
<td align="center">返回x+1后的自然对数</td>
<td align="center">Math.log1p(0);//0</td>
</tr>
<tr>
<td align="center">Math.log2(x) ES6新增</td>
<td align="center">返回x以2为底的对数</td>
<td align="center">Math.log2(8);//3</td>
</tr>
<tr>
<td align="center">Math.log10(x) ES6新增</td>
<td align="center">返回x以10为底的对数</td>
<td align="center">Math.log10(100);//2</td>
</tr>
<tr>
<td align="center">Math.cbrt(x) ES6新增</td>
<td align="center">返回x的立方根</td>
<td align="center">Math.cbrt(8);//2</td>
</tr>
<tr>
<td align="center">Math.clz32()  ES6新增</td>
<td align="center">返回一个数字在转换成 32位无符号整型数字的二进制形式后, 开头的 0 的个数</td>
<td align="center">Math.clz32(2);//30</td>
</tr>
<tr>
<td align="center">Math.hypot(x,y,z) ES6新增</td>
<td align="center">返回所有参数的平方和的平方根</td>
<td align="center">Math.hypot(3,4);//5</td>
</tr>
<tr>
<td align="center">Math.imul(x,y) ES6新增</td>
<td align="center">返回两个参数的类C的32位整数乘法运算的运算结果</td>
<td align="center">Math.imul(0xffffffff, 5);//-5</td>
</tr>
</tbody>
</table>
<h3>数值运算方法</h3>
<table>
<thead><tr>
<th align="center">方法名</th>
<th align="center">描述</th>
<th align="center">例子</th>
</tr></thead>
<tbody>
<tr>
<td align="center">Math.abs(x)</td>
<td align="center">返回x的绝对值</td>
<td align="center">Math.abs(-5);//5</td>
</tr>
<tr>
<td align="center">Math.floor(x)</td>
<td align="center">返回小于x的最大整数</td>
<td align="center">Math.floor(8.2);//8</td>
</tr>
<tr>
<td align="center">Math.ceil(x)</td>
<td align="center">返回大于x的最小整数</td>
<td align="center">Math.ceil(8.2);//9</td>
</tr>
<tr>
<td align="center">Math.trunc(x) ES6新增</td>
<td align="center">返回x的整数部分</td>
<td align="center">Math.trunc(1.23);//1</td>
</tr>
<tr>
<td align="center">Math.fround(x) ES6新增</td>
<td align="center">返回离它最近的<a href="https://en.wikipedia.org/wiki/Single-precision_floating-point_format">单精度浮点数</a>形式的数字</td>
<td align="center">Math.fround(1.1);//1.100000023841858</td>
</tr>
<tr>
<td align="center">Math.min(x,y,z)</td>
<td align="center">返回多个数中的最小值</td>
<td align="center">Math.min(3,1,5);//1</td>
</tr>
<tr>
<td align="center">Math.max(x,y,z)</td>
<td align="center">返回多个数中的最大值</td>
<td align="center">Math.max(3,1,5);//5</td>
</tr>
<tr>
<td align="center">Math.round(x)</td>
<td align="center">返回四舍五入后的整数</td>
<td align="center">Math.round(8.2);//8</td>
</tr>
<tr>
<td align="center">Math.random()</td>
<td align="center">返回0到1之间的伪随机数</td>
<td align="center">Math.random();</td>
</tr>
<tr>
<td align="center">Math.sign(x) ES6新增</td>
<td align="center">返回一个数的符号( 5种返回值, 分别是 1, -1, 0, -0, NaN. 代表的各是正数, 负数, 正零, 负零, NaN)</td>
<td align="center">Math.sign(-5);//-1</td>
</tr>
</tbody>
</table>
<h4>附:Number类型的数值运算方法</h4>
<p>Number.prototype中有一个方法叫做toFixed(), 用于将数值装换为指定小数位数的形式.</p>
<ul>
<li><p>没有参数或者参数为零的情况下, toFixed() 方法返回该数值的四舍五入后的整数形式, 等同于 Math.round(x);</p></li>
<li><p>其他情况下, 返回该数的指定小数位数的四舍五入后的结果.</p></li>
</ul>
<pre class="line-numbers"><code class="language-javascript">var num = 1234.56789;
console.log(num.toFixed(),num.toFixed(0));//1235,1235
console.log(num.toFixed(1));//1234.6
console.log(-1.235.toFixed(2));//-1.24</code></pre>
<h3>Math方法的一些规律</h3>
<p>以上, 数值运算中, 存在如下规律:</p>
<ol>
<li><p>Math.trunc(x) 方法当 ① x为正数时, 运算结果同 Math.floor(x); ② x为负数时, 运算结果同 Math.ceil(x). 实际上, 它完全可以由位运算替代, 且运算速度更快, 如 2.5&amp;-1 或 2.5|0 或 <del>2.5 或 2.5^0 , 它们的运算结果都为2; 如 -2.5&amp;-1 或 -2.5|0 或 </del>-2.5 或 -2.5^0 , 它们的运算结果都为-2;</p></li>
<li><p>Math.min(x,y,z) 与 Math.max(x,y,z) 方法由于可接无限个参数, 可用于求数组元素的最小最大值. 如: <code class="language-javascript">Math.max.apply(null,[5,3,8,9]); // 9</code> . 但是Math.min 不传参数返回 <code class="language-javascript">Infinity</code>, Math.max 不传参数返回 <code class="language-javascript">-Infinity</code> .</p></li>
<li><p>稍微利用 Math.random() 方法的特性, 就可以生成任意范围的数字. 如: 生成10到80之间的随机数, ~~(Math.random()*70 + 10);// 返回10~80之间的随机数, 包含10不包含80</p></li>
</ol>
<p>除去上述方法, Math作为对象, 继承了来之Object对象的方法. 其中一些如下:</p>
<pre class="line-numbers"><code class="language-javascript">Math.valueOf();//返回Math对象本身
+Math; //NaN, 试图转换成数字,由于不能转换为数字,返回NaN
Math.toString();//"[object Math]"</code></pre>
<h3>位运算</h3>
<p>Math对象提供的方法种类繁多, 且覆盖面非常全面, 基本上能够满足日常开发所需. 但同时我们也都知道, 使用Math对象的方法进行数值运算时, js代码经过解释编译, 最终会以二进制的方式进行运算. 这种运算方式效率较低, 那么能不能进一步提高运算的效率的呢? 如果我们使用位运算就可. 这是因为位运算本就是直接进行二进制运算.</p>
<h3>数值的二进制值</h3>
<p>由于位运算是基于二进制的, 因此我们需要先获取数值的二进制值. 实际上, toString 方法已经帮我们做好了一部分工作, 如下:</p>
<pre class="line-numbers"><code class="language-javascript">//正整数可通过toString获取
12..toString(2);//1100
//负整数问题就来了
(-12).toString(2);//-1100</code></pre>
<p>已知: 负数在计算机内部是采用补码表示的. 例如 -1, 1的原码是 0000 0001, 那么1的反码是 1111 1110, 补码是 1111 1111.</p>
<p>故: 负数的十进制转换为二进制时,符号位不变,其它位取反后+1. 即: <strong><code class="language-javascript">-x的二进制 = x的二进制取反+1</code></strong> . 由按位取反可借助^运算符, 故负整数的二进制可以借助下面这个函数来获取:</p>
<pre class="line-numbers"><code class="language-javascript">function getBinary(num){var s = (-num).toString(2),array = [].map.call(s,function(v){
      return v^1;});array.reduceRight(function(previousValue, value, index, array){
      var v = previousValue ^ value;array[index] = v;return +!v;},1);return array.join('');
}
getBinary(-12);//0100, 前面未补全的部分全部为1</code></pre>
<p>然后, 多试几次就会发现:</p>
<pre class="line-numbers"><code class="language-javascript">getBinary(-1) == 1..toString(2); //true
getBinary(-2) == 2..toString(2); //true
getBinary(-4) == 4..toString(2); //true
getBinary(-8) == 8..toString(2); //true</code></pre>
<p>这表明:</p>
<ul><li><p><strong>2的整数次方的值与它的相对数, 他们后面真正有效的那几位都相同</strong>.</p></li></ul>
<p>同样, 负数的二进制转十进制时, 符号位不变, 其他位取反后+1. 可参考:</p>
<pre class="line-numbers"><code class="language-javascript">function translateBinary2Decimal(binaryString){var array = [].map.call(binaryString,function(v){
      return v^1;});array.reduceRight(function(previousValue, value, index, array){
      var v = previousValue ^ value;array[index] = v;return +!v;},1);return parseInt(array.join(''),2);
}
translateBinary2Decimal(getBinary(-12));//12</code></pre>
<p>由上, 二进制转十进制和十进制转二进制的函数, 大部分都可以共用, 因此下面提供一个统一的函数解决它们的互转问题:</p>
<pre class="line-numbers"><code class="language-javascript">function translateBinary(item){var s = null,array = null,type = typeof item,symbol = !/^-/.test(item+'');switch(type){
      case "number":s = Math.abs(item).toString(2);if(symbol){return s;}break;case "string":if(symbol){return parseInt(item,2);}s = item.substring(1);break;default:return false;}
   //按位取反
   array = [].map.call(s,function(v){
      return v^1;});//+1
   array.reduceRight(function(previousValue, value, index, array){
      var v = (previousValue + value)==2;array[index] = previousValue ^ value;return +v;},1);s = array.join('');return type=="number"?'-'+s:-parseInt(s,2);
}
translateBinary(-12);//"-0100"
translateBinary('-0100');//-12</code></pre>
<h4>常用的二进制数</h4>
<table>
<thead><tr>
<th align="center">二进制数</th>
<th align="center">二进制值</th>
</tr></thead>
<tbody>
<tr>
<td align="center">0xAAAAAAAA</td>
<td align="center">10101010101010101010101010101010</td>
</tr>
<tr>
<td align="center">0x55555555</td>
<td align="center">01010101010101010101010101010101</td>
</tr>
<tr>
<td align="center">0xCCCCCCCC</td>
<td align="center">11001100110011001100110011001100</td>
</tr>
<tr>
<td align="center">0x33333333</td>
<td align="center">00110011001100110011001100110011</td>
</tr>
<tr>
<td align="center">0xF0F0F0F0</td>
<td align="center">11110000111100001111000011110000</td>
</tr>
<tr>
<td align="center">0x0F0F0F0F</td>
<td align="center">00001111000011110000111100001111</td>
</tr>
<tr>
<td align="center">0xFF00FF00</td>
<td align="center">11111111000000001111111100000000</td>
</tr>
<tr>
<td align="center">0x00FF00FF</td>
<td align="center">00000000111111110000000011111111</td>
</tr>
<tr>
<td align="center">0xFFFF0000</td>
<td align="center">11111111111111110000000000000000</td>
</tr>
<tr>
<td align="center">0x0000FFFF</td>
<td align="center">00000000000000001111111111111111</td>
</tr>
</tbody>
</table>
<p>现在也可以使用上述方法来验证下常用的二进制值对不对. 如下:</p>
<pre class="line-numbers"><code class="language-javascript">translateBinary(0xAAAAAAAA);//"10101010101010101010101010101010"</code></pre>
<h3>按位与(&amp;)</h3>
<p>&amp;运算符用于连接两个数, 连接的两个数它们二进制补码形式的值每位都将参与运算, 只有相对应的位上都为1时, 该位的运算才返回1. 比如 3 和 9 进行按位与运算, 以下是运算过程:</p>
<pre class="line-numbers"><code class="language-javascript">    0011    //3的二进制补码形式
&amp;    1001    //9的二进制补码形式
--------------------
0001    //1,相同位数依次运算,除最后一位都是1,返回1以外, 其它位数由于不同时为1都返回0</code></pre>
<p>由上, 3&amp;9的运算结果为1. 实际上, 由于按位与(&amp;)运算同位上返回1的要求较为严苛, 因此, 它是一种趋向减小最大值的运算.(无论最大值是正数还是负数, 参与按位与运算后, 该数总是趋向减少二进制值位上1的数量, 因此总是有值减小的趋势. ) 对于按位与(&amp;)运算, 满足如下规律:</p>
<ol>
<li><p><strong>数值与自身(或者-1)按位与运算返回数值自身</strong>.</p></li>
<li><p><strong>2的整数次方的值与它的相对数按位与运算返回它自身</strong>.</p></li>
<li><p><strong>任意整数与0进行按位与运算, 都将会返回0</strong>.</p></li>
<li><p><strong>任意整数与1进行按位与运算, 都只有0 或1 两个返回值</strong>.</p></li>
<li><p><strong>按位与运算的结果不大于两数中的最大值</strong>.</p></li>
</ol>
<p>由公式1, 我们可以对非整数取整. 即 <code class="language-javascript">x&amp;x === x&amp;-1 === Math.trunc(x)</code> 如下:</p>
<pre class="line-numbers"><code class="language-javascript">console.log(5.2&amp;5.2);//5
console.log(-5.2&amp;-1);//-5
console.log(Math.trunc(-5.2)===(-5.2&amp;-1));//true</code></pre>
<p>由公式4, 我们可以由此判断数值是否为奇数. 如下:</p>
<pre class="line-numbers"><code class="language-javascript">if(1 &amp; x){//如果x为奇数,它的二进制补码形式最后一位必然是1,1进行按位与运算后,将返回1,1又会隐式转换为true
   console.log("x为奇数");
}</code></pre>
<h3>按位或(|)</h3>
<p>|不同于&amp;, |运算符连接的两个数, 只要其二进制补码形式的各位上有一个为1, 该位的运算就返回1, 否则返回0. 比如 3 和 12 进行按位或运算, 以下是运算过程:</p>
<pre class="line-numbers"><code class="language-javascript">    0011    //3的二进制补码形式
|    1100    //12的二进制补码形式
--------------------
1111    //15, 相同位数依次运算,1返回1,故最终结果为41.</code></pre>
<p>由上, 3|12的运算结果为15. 实际上, 由于按位与(&amp;)运算同位上返回0的要求较为严苛, 因此, 它是一种趋向增大最小值的运算. 对于按位或(|)运算, 满足如下规律:</p>
<ol>
<li><p><strong>数值与自身按位或运算返回数值自身</strong>.</p></li>
<li><p><strong>2的整数次方的值与它的相对数按位或运算返回它的相对数</strong>.</p></li>
<li><p><strong>任意整数与0进行按位或运算, 都将会返回它本身</strong>.</p></li>
<li><p><strong>任意整数与-1进行按位或运算, 都将返回-1</strong>.</p></li>
<li><p><strong>按位或运算的结果不小于两数中的最小值</strong>.</p></li>
</ol>
<p>稍微利用公式1, 我们便可以将非整数取整.   即 <strong><code class="language-javascript">x|0 === Math.trunc(x)</code></strong> 如下:</p>
<pre class="line-numbers"><code class="language-javascript">console.log(5.2|0);//5
console.log(-5.2|0);//-5
console.log(Math.trunc(-5.2)===(-5.2|0));//true</code></pre>
<p>为什么 5.2|0 运算后会返回5呢? 这是因为浮点数并不支持位运算, 运算前, 5.2会转换为整数5再和0进行位运算, 故, 最终返回5.</p>
<h3>按位非(~)</h3>
<p>~运算符, 返回数值二进制补码形式的反码. 什么意思呢, 就是说一个数值二进制补码形式中的每一位都将取反, 如果该位为1, 取反为0, 如果该位为0, 取反为1. 我们来举个例子理解下:</p>
<pre class="line-numbers"><code class="language-javascript">~    0000 0000 0000 0000 0000 0000 0000 0011    //332位二进制补码形式
--------------------------------------------
1111 1111 1111 1111 1111 1111 1111 1100    //按位取反后为负数(最高位(第一位)表示正负,1代表负,0代表正)
--------------------------------------------
1000 0000 0000 0000 0000 0000 0000 0011    //负数的二进制转换为十进制时,符号位不变,其它位取反(+1)
1000 0000 0000 0000 0000 0000 0000 0100 // +1
--------------------------------------------
-4     //最终运算结果为-4</code></pre>
<p>实际上, 按位非(~)操作不需要这么兴师动众地去计算, 它有且仅有一条运算规律:</p>
<ul><li><p><strong>按位非操作一个数值, 等同于这个数值加1然后符号改变. 即: <code class="language-javascript">~x === -x-1</code></strong>.</p></li></ul>
<pre class="line-numbers"><code class="language-javascript">~5 ==&gt; -5-1 === -6;
~-2016 ==&gt; 2016-1 === 2015;</code></pre>
<p>由上述公式可推出: <strong><code class="language-javascript">~~x === -(-x-1)-1 === x</code></strong>. 由于位运算摈除小数部分的特性, 连续两次按位非也可用于将非整数取整. 即, <strong><code class="language-javascript">~~x === Math.trunc(x)</code></strong> 如下:</p>
<pre class="line-numbers"><code class="language-javascript">console.log(~~5.2);//5
console.log(~~-5.2);//-5
console.log(Math.trunc(-5.2)===(~~-5.2));//true</code></pre>
<p>按位非(~)运算符只能用来求数值的反码, 并且还不能输出反码的二进制字符串. 我们来稍微扩展下, 使它变得更易用.</p>
<pre class="line-numbers"><code class="language-javascript">function waveExtend(item){var s = typeof item == 'number' &amp;&amp; translateBinary(~item);return typeof s == 'string'?s:[].map.call(item,function(v){
      return v==='-'?v:v^1;}).join('').replace(/^-?/,function(m){return m==''?'-':''});
}
waveExtend(-8);//111 -8反码,正数省略的位全部为0
waveExtend(12);//-0011 12的反码,负数省略的位全部为1</code></pre>
<p>实际上, 按位非(~)运算符要求其运算数为整型, 如果运算数不是整型, 它将和其他位运算符一样尝试将其转换为32位整型, 如果无法转换, 就返回NaN. 那么~NaN等于多少呢?</p>
<pre class="line-numbers"><code class="language-javascript">console.log(~function(){alert(20);}());//alert(20),然后输出-1</code></pre>
<p>以上语句意在打印一个自执行函数的按位非运算结果. 而该自执行函数又没有显式指定返回值, 默认将返回undefined. 因此它实际上是在输出~undefined的值. 而undefined值不能转换成整型, 通过测试, 运算结果为-1(即~NaN === -1). 我们不妨来看看下来测试, 以便加深理解.</p>
<pre class="line-numbers"><code class="language-javascript">console.log(~'abc');//-1
console.log(~[]);//-1
console.log(~{});//-1
console.log(~function(){});//-1
console.log(~/\d/);//-1
console.log(~Infinity);//-1
console.log(~null);//-1
console.log(~undefined);//-1
console.log(~NaN);//-1</code></pre>
<h3>按位异或(^)</h3>
<p>^运算符连接的两个数, 它们二进制补码形式的值每位参与运算, 只有相对应的每位值不同, 才返回1, 否则返回0. <br>(相同则消去, 有些类似两两消失的消消乐). 如下:</p>
<pre class="line-numbers"><code class="language-javascript">    0011    //3的二进制补码形式
^    1000    //8的二进制补码形式
--------------------
1011    //11, 相同位数依次运算, 值不同的返回1</code></pre>
<p>对于按位异或(^)操作, 满足如下规律:</p>
<ol>
<li><p><strong>由于按位异或位运算的特殊性, 数值与自身按位异或运算返回0</strong>. 如: <code class="language-javascript">8^8=0</code> , 公式为 <code class="language-javascript">a^a=0</code> .</p></li>
<li><p><strong>任意整数与0进行按位异或运算, 都将会返回它本身</strong>. 如: <code class="language-javascript">0^-98=-98</code> , 公式为 <code class="language-javascript">0^a=a</code>.</p></li>
<li><p><strong>任意整数x与1(2的0次方)进行按位异或运算, 若它为奇数, 则返回 <code class="language-javascript">x-1</code>, 若它为偶数, 则返回 <code class="language-javascript">x+1</code> </strong>. 如: <code class="language-javascript">1^-9=-10</code> , <code class="language-javascript">1^100=101</code>  . 公式为 <code class="language-javascript">1^奇=奇-1</code> , <code class="language-javascript">1^偶=偶+1</code> ; 推而广之, <strong>任意整数x与2的n次方进行按位异或运算, 若它的二进制补码形式的倒数第n+1位是1, 则返回 <code class="language-javascript">x-2的n次方</code>, 反之若为0, 则返回 <code class="language-javascript">x+2的n次方</code> </strong>.</p></li>
<li><p><strong>任意整数x与-1(负2的1次方+1)进行按位异或运算, 则将返回 <code class="language-javascript">-x-1</code>, 相当于~x运算 </strong>. 如: <code class="language-javascript">-1^100=-101</code> , <code class="language-javascript">-1^-9=8</code> . 公式为 <code class="language-javascript">-1^x=-x-1=~x</code> .</p></li>
<li><p><strong>任意整数连续按位异或两次相同的数值, 返回它本身</strong>. 如: <code class="language-javascript">3^8^8=3</code> , 公式为 <code class="language-javascript">a^b^b=a</code> 或 <code class="language-javascript">a^b^a=b</code> .</p></li>
<li><p><strong>按位异或满足操作数与运算结果3个数值之间的交换律: 按位异或的两个数值, 以及他们运算的结果, 共三个数值可以两两异或得到另外一个数值</strong> . 如: <code class="language-javascript">3^9=10</code> , <code class="language-javascript">3^10=9</code> , <code class="language-javascript">9^10=3</code> ; 公式为 <code class="language-javascript">a^b=c</code> , <code class="language-javascript">a^c=b</code> , <code class="language-javascript">b^c=a</code> .</p></li>
</ol>
<p>以上公式中, 1, 2, 3和4都是由按位异或运算特性推出的, 公式5可由公式1和2推出, 公式6可由公式5推出.</p>
<p>由于按位异或运算的这种可交换的性质, 我们可用它辅助交换两个整数的值. 如下, 假设这两个值为a和b:</p>
<pre class="line-numbers"><code class="language-javascript">var a=1,b=2;
//常规方法
var tmp = a;
a=b;
b=tmp;
console.log(a,b);//2 1

//使用按位异或~的方法
a=a^b;    //假设a,b的原始值分别为a0,b0
b=a^b;    //等价于 b=a0^b0^b0 ==&gt; b=a0
a=a^b;    //等价于 a=a0^b0^a0 ==&gt; a=b0
console.log(a,b);//2 1
//以上可简写为
a^=b;b^=a;a^=b;</code></pre>
<h3>位运算小结</h3>
<p>由上可以看出:</p>
<ul>
<li><p>由于连接两个数值的位运算均是对相同的位进行比较操作, 故运算数值的先后位置并不重要, 这些位运算(&amp; | ^)满足交换律. 即: <code class="language-javascript">a操作符b === b操作符a</code>.</p></li>
<li><p>位运算中, 数字0和1都比较特殊. 记住它们的规律, 常可简化运算.</p></li>
<li><p>位运算(&amp;|~^)可用于取整, 同 Math.trunc().</p></li>
</ul>
<h3>有符号左移(&lt;&lt;)</h3>
<p>&lt;&lt;运算符, 表示将数值的32位二进制补码形式的除符号位之外的其他位都往左移动若干位数. 当x为整数时, 有: <code class="language-javascript">x&lt;&lt;n === x*Math.pow(2,n)</code> 如下:</p>
<pre class="line-numbers"><code class="language-javascript">console.log(1&lt;&lt;3);//8
console.log(100&lt;&lt;4);//1600</code></pre>
<p>如此, Math.pow(2,n) 便可简写为 1&lt;&lt;n.</p>
<h4>运算符之一为NaN</h4>
<p>对于表达式 <code class="language-javascript">x&lt;&lt;n</code> , <strong>当运算数x无法被转换为整数时,运算结果为0.</strong></p>
<pre class="line-numbers"><code class="language-javascript">console.log({}&lt;&lt;3);//0
console.log(NaN&lt;&lt;2);//0</code></pre>
<p><strong>当运算数n无法被转换为整数时,运算结果为x.</strong> 相当于 <code class="language-javascript">x&lt;&lt;0</code> .</p>
<pre class="line-numbers"><code class="language-javascript">console.log(2&lt;&lt;NaN);//2</code></pre>
<p><strong>当运算数x和n均无法被转换为整数时,运算结果为0.</strong></p>
<pre class="line-numbers"><code class="language-javascript">console.log(NaN&lt;&lt;NaN);//0</code></pre>
<h3>有符号右移(&gt;&gt;)</h3>
<p>&gt;&gt;运算符, 除了方向向右, 其他同&lt;&lt;运算符. 当x为整数时, 有: <code class="language-javascript">x&gt;&gt;n === x*Math.pow(2,-n)</code> . 如下:</p>
<pre class="line-numbers"><code class="language-javascript">console.log(-5&gt;&gt;2);//-2
console.log(-7&gt;&gt;3);//-1</code></pre>
<p>右移负整数时, 返回值最大为-1.</p>
<p>右移正整数时, 返回值最小为0.</p>
<p>其他规律请参考 有符号左移时运算符之一为NaN的场景.</p>
<h4>无符号右移(&gt;&gt;&gt;)</h4>
<p>&gt;&gt;&gt;运算符, 表示连同符号也一起右移. </p>
<p>注意:无符号右移(&gt;&gt;&gt;)会把负数的二进制码当成正数的二进制码. 如下:</p>
<pre class="line-numbers"><code class="language-javascript">console.log(-8&gt;&gt;&gt;5);//134217727
console.log(-1&gt;&gt;&gt;0);//4294967295</code></pre>
<p>以上, 虽然-1没有发生向右位移, 但是-1的二进制码, 已经变成了正数的二进制码. 我们来回顾下这个过程.</p>
<pre class="line-numbers"><code class="language-javascript">translateAry(-1);//-1,补全-1的二进制码至32: 11111111111111111111111111111111
translateAry('11111111111111111111111111111111');//4294967295</code></pre>
<p>可见, -1的二进制原码本就是32个1, 将这32个1当正数的二进制处理, 直接还原成十进制, 刚好就是 4294967295.</p>
<p>由此, 使用 &gt;&gt;&gt;运算符, 即使是右移0位, 对于负数而言也是翻天覆地的变化. 但是对于正数却没有改变. 利用这个特性, 可以判断数值的正负. 如下:</p>
<pre class="line-numbers"><code class="language-javascript">function getSymbol(num){return num === (num&gt;&gt;&gt;0)?"正数":"负数";
}
console.log(getSymbol(-100), getSymbol(123));//负数 正数</code></pre>
<p>其他规律请参考 有符号左移时运算符之一为NaN的场景.</p>
<h3>运算符优先级</h3>
<p>使用运算符, 如果不知道它们的运算优先级. 就像驾驶法拉利却分不清楚油门和刹车一样恐怖. 因此我为您准备了常用运算符的运算优先级表. 请对号入座.</p>
<table>
<thead><tr>
<th align="center">优先级</th>
<th align="center">运算符</th>
<th align="center">描述</th>
</tr></thead>
<tbody>
<tr>
<td align="center">1</td>
<td align="center">后置++ , 后置-- , [] , () 或 .</td>
<td align="center">后置++,后置--,数组下标,括号 或 属性选择</td>
</tr>
<tr>
<td align="center">2</td>
<td align="center">- , 前置++ , 前置-- , ! 或 ~</td>
<td align="center">负号,前置++,前置--, 逻辑非 或 按位非</td>
</tr>
<tr>
<td align="center">3</td>
<td align="center">* , / 或 %</td>
<td align="center">乘 , 除 或 取模</td>
</tr>
<tr>
<td align="center">4</td>
<td align="center">+ 或 -</td>
<td align="center">加 或 减</td>
</tr>
<tr>
<td align="center">5</td>
<td align="center">&lt;&lt; 或 &gt;&gt;</td>
<td align="center">左移 或 右移</td>
</tr>
<tr>
<td align="center">6</td>
<td align="center">&gt; , &gt;= , &lt; 或 &lt;=</td>
<td align="center">大于, 大于等于, 小于 或 小于等于</td>
</tr>
<tr>
<td align="center">7</td>
<td align="center">== 或 !=</td>
<td align="center">等于 或 不等于</td>
</tr>
<tr>
<td align="center">8</td>
<td align="center">&amp;</td>
<td align="center">按位与</td>
</tr>
<tr>
<td align="center">9</td>
<td align="center">^</td>
<td align="center">按位异或</td>
</tr>
<tr>
<td align="center">10</td>
<td align="center">或</td>
<td align="center">按位或</td>
</tr>
<tr>
<td align="center">11</td>
<td align="center">&amp;&amp;</td>
<td align="center">逻辑与</td>
</tr>
<tr>
<td align="center">12</td>
<td align="center">逻辑或</td>
<td align="center">逻辑或</td>
</tr>
<tr>
<td align="center">13</td>
<td align="center">?:</td>
<td align="center">条件运算符</td>
</tr>
<tr>
<td align="center">14</td>
<td align="center">=,/=,*=,%=,+=,-=,&lt;&lt;=,&gt;&gt;=,&amp;=,^=,按位或后赋值</td>
<td align="center">各种运算后赋值</td>
</tr>
<tr>
<td align="center">15</td>
<td align="center">,</td>
<td align="center">逗号</td>
</tr>
</tbody>
</table>
<p>可以看到, ① 除了按位非(~)以外, 其他的位运算符的优先级都是低于+-运算符的; ② 按位与(&amp;), 按位异或(^) 或 按位或(|) 的运算优先级均低于比较运算符(&gt;,&lt;,=等); ③位运算符中按位或(|)优先级最低.</p>
<h3>综合运用</h3>
<h3>计算绝对值</h3>
<p>使用有符号右移(&gt;&gt;)运算符, 以及按位异或(^)运算符, 我们可以实现一个 Math.abs方法. 如下:</p>
<pre class="line-numbers"><code class="language-javascript">function abs(num){var x = num&gt;&gt;31,    //保留32二进制中的符号位,根据num的正负性分别返回0-1
   y = num^x;    //返回正数,且利用按位异或中的公式2,num为正数,num^0则返回num本身;num为负数,则相当于num^-1,利用公式4, 此时返回-num-1
   return y-x;        //num为正数,则返回num-0num;num为负数则返回-num-1-(-1)|num|
}</code></pre>
<h3>比较两数是否符号相同</h3>
<p>通常, 比较两个数是否符号相同, 我们使用x*y&gt;0 来判断即可. 但如果利用按位异或(^), 运算速度将更快.</p>
<pre class="line-numbers"><code class="language-javascript">console.log(-17 ^ 9 &gt; 0);//false</code></pre>
<h3>对2的n次方取模(n为正整数)</h3>
<p>比如 123%8, 实际上就是求一个余数, 并且这个余数还不大于8, 最大为7. 然后剩下的就是比较二进制值里, 123与7有几成相似了. 便不难推出公式: <code class="language-javascript">x%(1&lt;&lt;n)==x&amp;(1&lt;&lt;n)-1</code> .</p>
<pre class="line-numbers"><code class="language-javascript">console.log(123%8);//3
console.log(123&amp;(1&lt;&lt;3)-1);//3 , 为什么-1时不用括号括起来, 这是因为-优先级高于&amp;</code></pre>
<h3>统计正数二进制值中1的个数</h3>
<p>不妨先判断n的奇偶性, 为奇数时计数器增加1, 然后将n右移一位, 重复上面步骤, 直到递归退出.</p>
<pre class="line-numbers"><code class="language-javascript">function getTotalForOne(n){return n?(n&amp;1)+arguments.callee(n&gt;&gt;1):0;
}
getTotalForOne(9);//2</code></pre>
<h3>实现加法运算</h3>
<p>加法运算, 从二进制值的角度看, 有 ①同位相加 和 ②遇2进1 两种运算(实际上, 十进制运算也是一样, 同位相加, 遇10进1). </p>
<p>首先我们看看第①种, 同位相加, 不考虑②遇2进1.</p>
<pre class="line-numbers"><code class="language-javascript">1 + 1 = 0
1 + 0 = 1
0 + 1 = 1
0 + 0 = 0</code></pre>
<p>以上运算过程有没有很熟悉. 是不是和按位异或(^)运算有着惊人的相似. 如:</p>
<pre class="line-numbers"><code class="language-javascript">1 ^ 1 = 0
1 ^ 0 = 1
0 ^ 1 = 1
0 ^ 0 = 0</code></pre>
<p>因此①同位相加的运算, 完全可由按位异或(^)代替, 即: x^y.</p>
<p>那么②遇2进1 应该怎么实现呢? 实际上, 非位移位运算中, 只有按位与(&amp;)才能满足遇2的场景, 且只有有符号左移(&lt;&lt;)能满足进1的场景.</p>
<p>现在范围缩小了, 就看&amp;和&lt;&lt;运算符能不能真正满足需要了. 值得高兴的是, 按位与(&amp;)只有在同位都是1的情况下才返回1, 其他情况均返回0. 如果对其运算结果再做左移一位的运算, 即: (x&amp;y)&lt;&lt;1. 刚好满足了②遇2进1的场景.</p>
<p>因为我们是将①同位相加和②遇2进1的两种运算分开进行. 那么最终的加法运算结果应该还要做一次加法. 如下:</p>
<p>最终公式: <code class="language-javascript">x + y = x^y + (x&amp;y)&lt;&lt;1</code> </p>
<p>这个公式并不完美, 因为它还是使用了加法, 推导公式怎么能直接使用推导结果呢? 太可怕了, 就不怕掉入递归深渊吗? 下面我们就来绕过这个坑. 而绕过这个坑有一个前提, 那就是只要 x^y 或 (x&amp;y)&lt;&lt;1中有一个值为0就行了, 这样便不用进行加法运算了. 讲了这么多, 不如看代码.</p>
<pre class="line-numbers"><code class="language-javascript">function add(x, y){var _x = x^y,_y = (x&amp;y)&lt;&lt;1;return !_x &amp;&amp; _y || !_y &amp;&amp; _x || arguments.callee(_x,_y);
}
add(12345678,87654321);//999999999
add(9527,-12);//9515</code></pre>
<h3>总结</h3>
<p>最后补充一点: 位运算一般只适用 [-2^31, 2^31-1] (即 -2147483648~2147483647) 以内的正负数. 超过这个范围, 计算将可能出现错误. 如下:</p>
<pre class="line-numbers"><code class="language-javascript">console.log(1&lt;&lt;31);//-2147483648 </code></pre>
<p>由于数值(2^31)超过了31位(加上保留的一个符号位,共32位), 故计算出错, 于是按照负数的方式解释二进制的值了.说好的不改变符号呢!!!</p>
<p>本文啰嗦几千字, 就为了说清楚两个事儿. ① Math对象中, 比较常用的就是数值运算方法, 不妨多看看, 其他的知道有这个api就行了. ② 位运算中, 则需要基本了解每种位运算符的运算方式, 如果能注意运算中 0和1等特殊数值 的一些妙用就更好了. 无论如何, 本文不可能面面俱到. 如果您对负数的位运算不甚理解, 建议去补下计算机的补码. 希望能对您有所帮助.</p>
<h3>注解</h3>
<ol>
<li><p><code class="language-javascript">相反数</code> : 只有符号不同的两个数, 我们就说其中一个是另一个的相反数.</p></li>
<li><p><code class="language-javascript">补码</code>: 在计算机系统中, 数值一律用补码来表示和存储, 且正数的原码和补码相同, 负数的补码等于其原码按位取反再加1.</p></li>
</ol>
<hr>
<p>本问就讨论这么多内容, 如果您有什么问题或好的想法欢迎在下方参与留言和评论.</p>
<p>本文作者: <a href="https://github.com/Louiszhai">louis</a></p>
<p>本文链接: <a href="http://louiszhai.github.io/2016/07/01/Math/">http://louiszhai.github.io/20...</a></p>
<p>参考文章</p>
<ul>
<li><p><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math">Math - JavaScript | MDN</a></p></li>
<li><p><a href="http://www.cnblogs.com/xljzlw/p/4231354.html?utm_source=tuicool&amp;utm_medium=referral">js中位运算的运用 - 那时候的我 - 博客园</a></p></li>
<li><p><a href="http://blog.csdn.net/yunyu5120/article/details/6692072">编程技巧--位运算的巧妙运用</a></p></li>
<li><p><a href="http://www.jb51.net/article/32130.htm">c语言中用位运算实现加法技巧介绍_C 语言_脚本之家</a></p></li>
</ul>

全面解读Math对象及位运算相关推荐

  1. Python之数据分析(Numpy中的除法和取余、三角函数、ufunc对象的位运算)

    文章目录 一.四种除法 二.取余运算 三.斐波那契数的四种求法 四.坐标变化的三角函数 五.ufunc对象位运算 一.四种除法 1.真除: 结果完全保留,小数部分也保留 1)numpy.true_di ...

  2. 小博老师解读经典Java面试题—Java位运算

    [面试原题] 我们经常会看到类似于下面这样的Java面试题: 请编写出效率最高的2乘以8的运算结果. [正确答案] 2<<3 [面试技术点] 面试者是否了解.灵活运用java位运算技术. ...

  3. 技术图文:位运算技术在求解算法题中的应用

    背景 前段时间,在知识星球立了一个Flag,这是总结Leetcode刷题的第一篇图文. 在总结这篇图文的时候,顺便把遇到的坑写了两篇辅助的图文,大家可以参考一下: 有符号整型的数据范围为什么负数比正数 ...

  4. 数据结构与算法--位运算

    位运算 记得大学编译原理刚开始面对的就是机器语言,而位运算就是讲数字用二进制标识后,对每一位上的0, 或者1 进行运算.二进制以及位运算都是计算机学科的基础,很多底层技术都离不开位运算,因此我们在实际 ...

  5. python 整数逆位运算_python训练营:注释、运算符、数据类型与位运算

    天学习的内容整理成脑图如下,其中带☆的是需要重点掌握的模糊知识点,需要加强训练和记忆. 二.具体学习内容 2.1 注释 2.1.1 多行注释 2.1.2 长字符串注释 2.2 运算符 2.2.1 算术 ...

  6. 洛谷P1896 [SCOI2005]互不侵犯 状压dp+位运算

    题目链接:https://www.luogu.org/problem/P1896 题意:n*n的格子填数,每个数填放位置的周围(8个)不能有其他的数 n<=9 ,矩形状压 f[i][j][s], ...

  7. 数组、字符串对象、Math对象

    数组的介绍 数组介绍 概念: 就是将若干个数据以一定的顺序放在一起的一个集合体,整体上就称之为"数组".数组就是一列数据的有序排列的集合. 定义形式: var arr1 = new ...

  8. 【转】C语言的位运算的优势

    位运算加速技巧 1. 如果乘上一个2的倍数数值,可以改用左移运算(Left Shift) 加速 300% x = x * 2; x = x * 64; //改为: x = x << 1; ...

  9. EF架构~为分组添加位运算聚合方法

    回到目录 我们知道在Linq里的分组groupby可以对集合中一个或者多个字段进行分组,并对其中一个属性进行聚合,而Linq为我们提供了多种聚合方法,由aver,sum,count等,而在大叔权限体系 ...

最新文章

  1. 2021全国高校计算机能力挑战赛(初赛)Java试题三
  2. 首届“陇剑杯”网络安全大赛线上赛圆满结束
  3. Fedora15使用笔记
  4. Android 解决不同进程发送KeyEvent 的问题
  5. Java及Android开发环境搭建
  6. 数据中心供电有多重要,看看这件事就知道了
  7. 利用ffmpeg转换mp4文件
  8. java输入流的控制_Java-Android-IO流-控制台输入输出
  9. 第一阶段 03Java的基本数据类型
  10. MSPA安装与生态源地提取
  11. Codeforces 165D Beard Graph 边权树剖+树状数组
  12. oneno浏览器插件_Send to OneNote 1.2.13
  13. 台达PLC解密次数限制
  14. SpringBoot+JWT+Shiro,linux 高级编程面试题
  15. oracle report builder 6i下载,Report Builder 3.0
  16. Evo使用过程问题汇总
  17. 运营100天的微信公众号能挣多少钱?
  18. openwrt php 编译环境,在linux下修改编译网件WNDR3700v4的Openwrt固件,支持128MB NAND
  19. CentOS7上安装Snipe-IT4.6.3详细过程及注意事项
  20. java List与json的转换

热门文章

  1. 图论(4)邻接矩阵,关联矩阵
  2. 【小程序开发之uni-app】通识和实战准备
  3. vue-cli+webpack 的项目中怎么导入bootstrap与jquery
  4. ACrush 回忆录
  5. 电机FOC控制的PI调节串并联转换问题理解
  6. android中获取mac地址8.0,华为Mate10手机emui8.0系统怎么查看IP地址和mac地址?
  7. ApiPost自动化测试基础之:接口参数依赖的情景处理
  8. linux网络存储备份,备份基础:Linux异构网络共享光盘刻录(下)
  9. 聚焦数据管理流程重塑
  10. MyBatis从入门到精通