如果你的第一门编程语言不是 JavaScript,而是 C++ 或 Java,那么一开始你大概会看不惯 JavaScript 的数字类型。在 JavaScript 中的数字类型是不区分什么 Int,Float,Double,Decimal 的。咳咳,我说的当然是在 ES6 之前的 JS,在 ES6 的新标准中提出了像 Int8Array 这样新的数据类型。不过这不是本文叙述的重点,暂且就不谈啦。本文将更着重地谈 JS 的数字类型以及作用于它的位操作符,而关于包装对象 Number 的更多了解可以看拔赤翻译的《JavaScript设计模式》

数字类型的本质

实际上,JavaScript的数字类型的本质就是一个基于 IEEE 754 标准的双精度 64 位的浮点数。按照标准,它的数据结构如图示这样:由1位符号位,11位指数部分以及52位尾数部分构成。

在浮点数中,数字通常被表示为:

(-1)<sup>sign</sup> × mantissa × 2<sup>exponent</sup>

而为了将尾数规格化,并做到尽量提高精确度,就需要把尾数精确在 [1,2) 的区间内,这样便可省去前导的1。比如:

11.101 × 2<sup>3</sup> = 1.1101 × 2<sup>4</sup> 0.1001 × 2<sup>5</sup> = 1.001 × 2<sup>4</sup>

并且标准规定指数部分使用 0x3ff 作为偏移量,也就有了双精度浮点数的一般公式:

(-1)<sup>sign</sup> × 1.mantissa × 2<sup>exponent - 0x3ff</sup>

举一些例子,应该能帮助你理解这个公式:

3ff0 0000 0000 0000  =  1
c000 0000 0000 0000 = -2
3fd5 5555 5555 5555 ~ 1/3
0000 0000 0000 0000 = 0
8000 0000 0000 0000 = -0
7ff0 0000 0000 0000 = 无穷大 ( 1/0 )
fff0 0000 0000 0000 = 负无穷大 ( 1/-0 )
7fef ffff ffff ffff ~ 1.7976931348623157 x 10^308 (= Number.MAX_VALUE)
433f ffff ffff ffff = 2^53 - 1 (= Number.MAX_SAFE_INTEGER)
c33f ffff ffff ffff = -2^53 + 1 (= Number.MIN_SAFE_INTEGER)

得益于尾数省略的一位“1”,使用双精度浮点数来表示的最大安全整数为 -2<sup>53</sup>+1 到 2<sup>53</sup>-1 之间,所以如果你仅仅使用 JavaScript 中的数字类型进行一些整数运算,那么你也可以近似地将这一数字类型理解为 53 位整型。

让人又爱又恨的位操作符

熟悉 C 或者 C++ 的同学一定对位操作符不陌生。位操作符最主要的应用大概就是作为标志位与掩码。这是一种节省存储空间的高明手段,在曾经内存的大小以 KB 为单位计算时,每多一个变量就是一份额外的开销。而使用位操作符的掩码则在很大程度上缓解了这个问题:

#define LOG_ERRORS            1  // 0001
#define LOG_WARNINGS 2 // 0010
#define LOG_NOTICES 4 // 0100
#define LOG_INCOMING 8 // 1000 unsigned char flags; flags = LOG_ERRORS; // 0001
flags = LOG_ERRORS | LOG_WARNINGS | LOG_INCOMING; // 1011

因为标志位一般只需要 1 bit,就可以保存,并没有必要为每个标志位都定义一个变量。所以按上面这种方式只使用一个变量,却可以保存大量的信息——无符号的 char 可以保存 8 个标志位,而无符号的 int 则可以同时表示 32 个标志位。

可惜位操作符在 JavaScript 中的表现就比较诡异了,因为 JavaScript 没有真正意义上的整型。看看如下代码的运行结果吧:

var a, b;

a = 2e9;   // 2000000000
a << 1; // -294967296 // fxck!我只想装了个逼用左移1位给 a * 2,但是结果是什么鬼!!! a = parseInt('100000000', 16); // 4294967296
b = parseInt('1111', 2); // 15
a | b; // 15 // 啊啊啊,为毛我的 a 丝毫不起作用,JavaScript真是门吊诡的语言!!!

好吧,虽然我说过大家可以近似地认为,JS 的数字类型可以表示 53 位的整型。但事实上,位操作符并不是这么认为的。在 ECMAScript® Language Specification 中是这样描述位操作符的:

The production A : A @ B, where @ is one of the bitwise operators in the productions above, is evaluated as follows:

  1. Let lref be the result of evaluating A.
  2. Let lval be GetValue(lref).
  3. Let rref be the result of evaluating B.
  4. Let rval be GetValue(rref).
  5. Let lnum be ToInt32(lval).
  6. Let rnum be ToInt32(rval).
  7. Return the result of applying the bitwise operator @ to lnum and rnum. The result is a signed 32 bit integer.

需要注意的是第5和第6步,按照ES标准,两个需要运算的值会被先转为有符号的32位整型。所以超过32位的整数会被截断,而小数部分则会被直接舍弃。

而反过来考虑,我们在什么情况下需要用到位操作符?使用左移来代替 2 的幂的乘法?Naive啊,等遇到像第一个例子的问题,你就要抓狂了。而且对一个浮点数进行左移操作是否比直接乘 2 来得效率高,这也是个值得商榷的问题。

那用来表示标志位呢?首先,现在的内存大小已经不值得我们用精简几个变量来减少存储空间了;其次呢,使用标志位也会使得代码的可读性大大下降。再者,在 JavaScript 中使用位操作符的地方毕竟太少,如果你执意使用位操作符,未来维护这段代码的人又对 JS 中的位操作符的坑不熟悉,这也会造成不利的影响。

所以,我对大家的建议是,尽量在 JavaScript 中别使用位操作符。

为什么不要在 JavaScript 中使用位操作符?的更多相关文章

  1. javascript 中的new操作符的理解

    new 操作符 在有上面的基础概念的介绍之后,在加上new操作符,我们就能完成传统面向对象的class + new的方式创建对象,在Javascript中,我们将这类方式成为Pseudoclassic ...

  2. JavaScript 中的相等操作符 ( 详解 [] == []、[] == ![]、{} == !{} )

    ECMAScript 中的相等操作符由两个等于号 ( == ) 表示,如果两个操作数相等,则返回 true. 相等操作符会先转换操作数(通常称为强制转型),然后比较它们的相等性. 在转换不同的数据类型 ...

  3. 浅谈Javascript中的void操作符

    由于JS表达式偏啰嗦,于是最近便开始采用Coffeescript来减轻负担.举个栗子,当我想取屋子里的第一条dog时,首先要判断house对象是否存在,然后再判断house.dogs是否存在,最后取h ...

  4. JavaScript中的十种操作符

    ①   一元操作符(参与的只有一个变量) 前置递增递减(语句解析到递增/递减时值就被改变了) 后置递增递减(整个语句执行后值再改变) 递增递减也可用于字符串,布尔值,对象等,结果都将是数值:   ;v ...

  5. 通过例子深入理解javascript中的new操作符

    1.首先看一道题目 function Cat(name,age){ this.name=name; this.age=age; } console.log(new Cat('miaomiao',18) ...

  6. Javascript中的位运算符和技巧

    ECMAScript 整数有两种类型,即有符号整数(允许用正数和负数)和无符号整数(只允许用正数).在 ECMAScript 中,所有整数字面量默认都是有符号整数,这意味着什么呢? 有符号整数使用 3 ...

  7. JavaScript中的typeof操作符用法实例

    在Web前端开发中,我们经常需要判断变量的数据类型.鉴于ECMAScript是松散类型的,因此需要有一种手段来检测给定变量的数据类型——typeof就是负责提供这方便信息的操作符.   对一个值使用t ...

  8. javascript中的位运算,

    罗浮宫群里又有讨论位运算符号|了,做过一段时间php,数据库保存布尔值数据经常用到,比如100110 就表明了六个属性的是与否,极大减少了数据量..] ECMAScript 中位运算跟其他语言一样的. ...

  9. javascript中的一元操作符

    题目如下: var s1 = "01"; var s2 = "1.1"; var s3 = "z"; var b = false; var ...

随机推荐

  1. .NET面试题系列(六)多线程

    1.多线程的三个特性:原子性.可见性.有序性 原子性:是指一个操作是不可中断的.即使是多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰. 比如,对于一个静态全局变量int i,两个线程同 ...

  2. python AjaxSpider 代码演示

    import re # 引入正则表达式 import json # 引入 json import pymongo # 引入mongo数据库 import requests # 引入HTTP请求协议 f ...

  3. Codeforces 238 div2 A. Gravity Flip

    题目链接:http://codeforces.com/contest/405/problem/A 解题报告:有n列箱子竖直放置,每列箱子上都有数量不等的箱子,这些箱子之间没有固定,当重力方向改为平行向 ...

  4. 图的最短路径-----------SPFA算法详解(TjuOj2831_Wormholes)

    这次整理了一下SPFA算法,首先相比Dijkstra算法,SPFA可以处理带有负权变的图.(个人认为原因是SPFA在进行松弛操作时可以对某一条边重复进行松弛,如果存在负权边,在多次松弛某边时可以更新该 ...

  5. [转]linux各文件夹介绍

    本文来自linux各文件夹的作用的一个精简版,作为个人使用笔记. 下面简单看下linux下的文件结构,看看每个文件夹都是干吗用的? /bin 二进制可执行命令 /dev 设备特殊文件 /etc 系统管 ...

  6. Javascript - Vue - 过滤器

    过滤器 输出的数据由vue对象提供,但它的数据可能需要做进一步的处理才适合展示给用户看,为此,可以在静态的Vue上定义一个过滤器对实例vue对象的data数据进行过滤处理. //调用过滤器//msg是 ...

  7. 记录自己对EventLoop和性能问题处理的一点心得【转】

    转自:http://www.cnblogs.com/lanyuliuyun/p/4483384.html 1.EventLoop 这里说的EventLoop不是指某一个具体的库或是框架,而是指一种程序 ...

  8. MySQL乱码问题以及utf8mb4字符集

    MySQL乱码问题以及utf8mb4字符集 1.乱码 推荐大家看 深入MySQL字符集设置 ,区分检查client端.server端的编码:最简单暴力的方式,是在所有的环节都显式明确的指定相同的编码, ...

  9. TypeError: Object of type 'int64' is not JSON serializable

    错误类型:TypeError: Object of type 'int64' is not JSON serializable 错误场景:对Numpy和Pandas结果进行json.dumps报错 错 ...

  10. Restful对于URL的简化

    REST是英文representational state transfer(表象性状态转变)或者表述性状态转移,它是web服务的一种架构风格.使用HTTP,URI,XML,JSON,HTML等广泛流 ...