关于 JavaScript 数字的那些事

本文总结 JavaScript 语言中和数字相关的语言特性、缺陷与陷阱及使用技巧。相关基础内容请参考语言规范,本文只涉及较容易被疏忽的方面。首先分享一个经验:我在使用 JavaScript 做大数计算或高精度计算时有个原则:不要使用 JavaScript 做这件事情! 如果某个运算过程你可以选择在 Java 中或 JavaScript 中完成,尽可能不要使用 JavaScript 做数学运算!

本文所有示例内容在如下环境测试:

$ node -v
v4.0.0
$ node -p process.versions.v8
4.5.103.30

Safari 版本 9.0 (11601.1.56)

1. 数字字面值也是对象

在 JavaScript 中,数字的字面值也是对象。可以使用它直接调用方法:

$ 3.1415926.toFixed(4)
'3.1416'

然而使用整数直接调用方法会抛出 SyntaxError ,这是因为解释器不能正确解析,它试图将点操作符解释成浮点数的一部分:

syntaxerror.png

克服这种情况非常简单,有下面三种方法:

123..toString();
123 .toString();
(123).toString();

2. 两个特殊数值:NaN 和 Infinity

2.1. NaN

NaN 代表“不是一个数字”,而它的类型却是 number 。实际使用中,它通常产生自——从表单取数据并转换成数字类型:

var age = document.getElementById('age').value;
Number(age); // 用户输入 'abc' 时此处则为 `NaN`

识别 NaN 不能依赖等同判断( === ),甚至普通的相等判断( == )也不行:

$ Number('1.2x') === NaN
false
$ Number('abc') == NaN
false

需要使用 isNaN 方法:

$ isNaN(NaN)
true

然而此方法会尝试将其参数动态转换成 number 类型再判断,因此对于 stringobject 等类型的检测,它通常会返回 true :

isNaN('xyz');    // true
isNaN(['abc']);  // true
isNaN({});      // true
isNaN([123]);    // false

为了正确的检测出 NaN 可以使用一个小技巧,它基于这样一个事实:目前只有 NaN 是不等于自身的对象:

function isRealNaN (x) {
    return x !== x;
}

console.log(isRealNaN(NaN));  // true
console.log(isRealNaN({}));    // false
console.log(isRealNaN('foo')); // false

更为正统的方式是这样的:

function isRealNaN (x) {
    return typeof x === 'number' && isNaN(x);
}

当然,在实现了 ECMAScript 6 的解释器环境中,也可以使用 Number.isNaN() 方法。

2.2. Infinity

Infinity 实际开发中出现的频率似乎并不高。它代表无穷大,对应的还有 -Infinity 代表无穷小。看几个例子即可:

123 / 0              // Infinity
Infinity + 1        // Infinity
Infinity + Infinity  // Infinity
Infinity - Infinity  // NaN

3. 数字的显示方式

  1. 整数位数超过 21 位时显示成科学计数法:
$ 1234567890123456789012
1.2345678901234568e+21
$ 123456789012345678901
123456789012345680000
  1. 0. 开头,后面跟着超过 5 个零时,显示成科学计数法:
$ 0.0000001
1e-7
$ 0.000001
0.000001
  1. 其它情况下正常显示。

4. 总是不准的数学运算

如果你有一定的 JavaScript 开发经验,一定遇到过浮点数算不准确的问题:

$ 0.1 + 0.2 === 0.3
false
$ 0.2 * 0.2 === 0.04
false

然而事情似乎并不只是发生在浮点数身上:

$ 9007199254740992 + 1
9007199254740992
$ 9007199254740992 + 2
9007199254740994

这是因为事实上 JavaScript 中的一切数字实际存储的均为 64 bit 浮点数,采用 IEEE 754 规范。

5. 不尽如人意的四舍五入

toFixed() 方法可把 Number 四舍五入为指定小数位数的数字。

以上是 w3school 对 toFixed() 方法的解释,然而此方法并不尽如人意。

当数字大于 21 位时,toFixed() 方法只是调用 toString() 返回科学计数法:

$ (1234567890123456789012).toFixed(2)
'1.2345678901234568e+21'

由于浮点数精度问题,toFixed() 方法实际上只能正确做到“四舍六入”,对于“五”,它的处理看上去并不那么精确:

$ 1.105.toFixed(2)
'1.10'

关于此方法还有“四舍六入五成双”的解释:

  1. 被修约的数字小于5时,该数字舍去;
  2. 被修约的数字大于5时,则进位;
  3. 被修约的数字等于5时,要看5前面的数字,若是奇数则进位,若是偶数则将5舍掉,即修约后末尾数字都成为偶数;若5的后面还有不为“0”的任何数,则此时无论5的前面是奇数还是偶数,均应进位。

然而这也并不准确,可以参考下面的结果:

1.105  '1.10'
1.115  '1.11'
1.125  '1.13'
1.135  '1.14'
1.145  '1.15'
1.155  '1.16'
1.165  '1.17'
1.175  '1.18'
1.185  '1.19'
1.195  '1.20'

有种比较流行的修正方法

function myToFixed(value, precision) {
    precision = precision || 0;
    var pow = Math.pow(10, precision);
    return (Math.round(value * pow) / pow).toFixed(precision);
}

它的计算结果看上去准确多了:

1.105  '1.11'
1.115  '1.12'
1.125  '1.13'
1.135  '1.14'
1.145  '1.15'
1.155  '1.16'
1.165  '1.17'
1.175  '1.18'
1.185  '1.19'
1.195  '1.20'

然而这个世界对程序员并没有那么善意:

    console.log(myToFixed(2.135, 2));
    // 输出 2.13

最后,我就不告诉你IE7的结果不尽相同了。而其它浏览器的计算结果是否和我的环境相同,我也不敢告诉你答案,因为我不确定。至于如何完全正确地四舍五入,我并没有答案,也有人采用转成字符串进而进行正则匹配的方法,这在特定的场合下也许可行,但很难写出通用的方法。

6. 取随机整数

JavaScript 中缺少产生随机整数的方法,但是可以用 Math.random() 自行实现。参考代码如下:

function nextInt(n) {
    var ret = parseInt(Math.random() * n);
    if (ret > n || ret < 0 ) {
        ret = n;
    }
    return ret;
}

产生的随机数分布的还算均匀。使用如下代码测试:

function test(x) {
    var foo = {};
    for (var i = 10000 - 1; i >= 0; i--) {
        var s = nextInt(x);
        if (foo[s]) {
            foo[s] = foo[s] + 1;
        } else {
            foo[s] = 1;
        }
    }
    console.log(foo);
}

test(2);
test(10);

输出:

nextint.png

参考资料:

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

推荐阅读更多精彩内容

  • 在编程中我们总要进行一些数学运算以及数字处理,尤其是浮点数的运算和处理,这篇文章主要介绍C语言下的数学库。而其他语...
    欧阳大哥2013阅读 5,088评论 0 12
  • 官方中文版原文链接 感谢社区中各位的大力支持,译者再次奉上一点点福利:阿里云产品券,享受所有官网优惠,并抽取幸运大...
    HetfieldJoe阅读 1,320评论 0 15
  • 特别说明,为便于查阅,文章转自https://github.com/getify/You-Dont-Know-JS...
    杀破狼real阅读 190评论 0 0
  • 故事一 岸 “滴滴…………” 床头的闹钟不安分地吵闹着,似乎故意想打乱这久违的平静。 “已经早上了啊?!” 我...
    心理盐水Z阅读 276评论 1 1
  • 今天中午,为为趁着午读的时间给萤火虫们上了一堂以“安全第一”为主题的班会课。 开这个主题班会是因为在网...
    露露老师阅读 233评论 0 0