JavaScript中的数字值

JavaScript中的数字运算大概是最让人迷惑的了,我看过许多讲述JS怪异之处的资料都会举一个列子:

0.1 + 0.2; // 0.30000000000000004

除了跟风喊几句什么鬼之外,让我们一起来探索一下隐藏在背后的东西。在计算机的世界,有果必有因。

首先要明确一个概念,JavaScript中的所有数字都是浮点数,并且是符合IEEE754标准的双精度浮点数。一般我们看到的类似整数的东西其实都是浮点数,只不过是小数点后没有数字,不显示小数部分而已。

数字字面量

尽管语言内部表示只有浮点数,但数字字面量表示还是可以有整数和浮点数的。因此我们可以直接输入35,而不用35.0来表示这个数字。

JS中的数字还有几个特殊值:

  • 表示错误的值:NaN和Infinity
  • 用于某些数学计算的+0和-0

这几个特殊值很容易让人摸不清头脑,他们本身也有许多反直觉的地方,且让我细细道来。

NaN

它是Not A Number的缩写,表示值不是数字。比如尝试将'aa'解析为数字:

 Number('aa');  // NaN

看起来好像很简单,但这里有不少陷阱,一不小心就会给你带来难以察觉的bug。首先虽然NaN表示的是不是数字,但如果我们用typeof检查它自身的类型:

 typeof NaN;  //Number

表示不是数字的东西自身却是个数字,这个逻辑让我不敢直视。

然后如果我们严格比较两个NaN:

NaN === NaN  //false

好吧,明明是一样的东西,类型也同是Number,居然是不相等的!NaN也是JS中唯一一个不等于自身的值。所有要判断某个值是否是NaN不能直接比较,而要用原生的isNaN方法:

isNaN(NaN);  //true

这还没有完,如果你将非数字字面量传入isNaN方法,你很可能得到的时true!

isNaN('aaa');  //true

可'aaa'明明不是NaN啊!这是因为'aaa'先被隐性转换成数字,然后再传入isNaN,也就是类似:

isNaN(Number('aaa'));

前面提过,Number('aaa')的结果是NaN,所以isNaN自然为true。要躲过这个陷阱,必须在判断时多加一个判断是否是数字的条件:

if (typeof value === 'number' && isNaN(value))

还有一个更简单的方法,利用前面提到的NaN是唯一一个不和自己相等的东西:

if (value !== value)

于是只要value是NaN,这里就一定为true,其他情况则一律为false。

Infinity

再来看看Infinity,这个值通常用来表示一个超出表示范围的值,当一个数字除0的时候也会返回Infinity。Infinity同样有+Infinity和-Infinity。比起NaN,Infinity要友善的多,你可以直接用===来判断,也可以用内置的isFinite()来做判断。

+0/- 0

最后再来看看正零和负零。这样反常识的表示方法在某些数学运算领域是很有用的,比如在表示趋于零的极限时,正零和负零可以帮助我们表示趋近的方向。但一般情况下,我们可粗略的认为只有一个0,不需要做特别的区分。

(-0).toString()  //'0'     (+0).toString()  //'0'

数字的内部表示

本文的开头说过,JavaScipt中的所有数字都是64位的双精度浮点数。这里我们就来详细看看这种浮点数在内部是如何表示的,以及这种表示方法的一些问题和局限。

JS中的浮点数由三部分组成:

  • 符号占1位
  • 指数部分占11位
  • 小数部分占52位

这三部分加起来就是64位了。


64位浮点数的内部表示
64位浮点数的内部表示

而计算机内部都是二进制实现的,所以数字计算公式为:

(–1)sign × %1.fraction × 2exponent

%表示二进制。

这样的表示方法有什么问题呢?这就是开篇提出的那个例子:

0.1 + 0.2; // 0.30000000000000004

更奇怪的是,按常识我们都知道加法的结合律,既(a+b)+c = a+(b+c)。但在JS中这个公里不成立:

(0.1 + 0.2) + 0.3; // 0.6000000000000001     0.1 + (0.2 + 0.3); // 0.6

让我们来详细探讨一下这个问题是怎么产生的。我们先来看看我们习惯使用的十进制。十进制的小数可以用分数的形式表示:m/10^e

可以看到,分母部分都是十的次方。十进制也有不能精确表示的分数,比如1/3就不能被精确表示。这是因为分母不含3,因此必然无法表示为10^e。

再来看二进制,同样的分数表示法,只不过分母是2的次方而已。因此我们可以得出结论,只要分母部分不是2的次方的,都无法精确表示为2^e:

0.5dec = 5/10 = 1/2 = 01bin
0.75dec = 75/100 = 3/4 = 0.11bin

0.1dec = 1/10 = 1/2X5
0.2dec = 2/10 = 1/5

现在你明白为什么 0.1 + 0.2是0.30000000000000004了吧。我们在console里看到的0.1并不是完整的内部表示,稍微做点处理就能看到内部表示了:

0.1 * Math.pow(10, 24)  //1.0000000000000001e+23

那么在需要精确计算的时候有什么方法吗?有的,那就是使用整数。整数没有小数部分的舍入问题,可以准确地被表示。例如在金融方面,可以按最小单位的整数来表示钱。比如0.55元表示为55分,按55来进行计算。

另外也要注意,直接比较小数可能会带来不可知的结果,最好使用Machine_epsilon来进行比较:

var EPSILON = Math.pow(2, -53);
function epsEqu(x, y) {    
  return Math.abs(x - y) < EPSILON;
}

整数

前面说过,JS中并不存在真正的整数。整数都是用浮点数表示的,但是要注意有一处例外的地方,那就是位运算。JS中的位运算会先将数字转换为32位整数,运算完成后返回的结果也是32位整数。

另外,由于整数是由浮点数表示的,这里有一个安全整数的概念。JS中的安全整数指的是范围在(−253, 253)内的整数。我们说他们是安全的,是因为在这个范围内可以保证每个整数只有一个对应的浮点数表示形式。超过这个范围则会出现多个浮点表示形式。因此在JS中做整数运算时,最好保证运算的整数都在这个安全范围之内。一定要处理大整数的话必须依赖相应的类库,否则结果很可能不准确。

在写完这篇文章之后,JS之父Brendan Eich透露了在ES7中会支持64位大整数。JS渐渐摆脱了玩具语言的束缚,向更广阔的天地出发了。跟上了,程序员。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容

  • 在编程中我们总要进行一些数学运算以及数字处理,尤其是浮点数的运算和处理,这篇文章主要介绍C语言下的数学库。而其他语...
    欧阳大哥2013阅读 5,183评论 0 12
  • 什么是 JavaScript 语言? JavaScript 是一种轻量级的脚本语言。所谓“脚本语言”(script...
    oWSQo阅读 1,790评论 0 1
  • 整数和浮点数 规则在JavaScript语言的底层,根本没有整数,所有数字都是小数(64位浮点数)JavaScri...
    素弥阅读 1,072评论 0 0
  • 好吧,本来想理解理解js数字存储原理的。看来我还是嫩,只有先记下一些重要的先了。参考链接:http://javas...
    jxnu薛哥阅读 311评论 0 2
  • 在人间,有谁活得不像一是一场炼狱,我不哭,我已没有尊严能放弃! 我们不一样,每个人都有不同的际遇,我们在这里,在这...
    铁血阿飞阅读 868评论 0 2