5. 数字和 Math 的新特性
目录
请支持本书:购买 (PDF, EPUB, MOBI)捐赠
(广告,请不要屏蔽。)

5. 数字和 Math 的新特性



5.1 概述

5.1.1 新的整数字面量

您现在可以使用二进制和八进制表示法指定整数

> 0xFF // ES5: hexadecimal
255
> 0b11 // ES6: binary
3
> 0o10 // ES6: octal
8

5.1.2 新的 Number 属性

全局对象 Number 获得了一些新的属性

5.1.3 新的 Math 方法

全局对象 Math 具有用于数值、三角函数和位运算的新方法。让我们看四个例子。

Math.sign() 返回数字的符号

> Math.sign(-8)
-1
> Math.sign(0)
0
> Math.sign(3)
1

Math.trunc() 删除数字的小数部分

> Math.trunc(3.1)
3
> Math.trunc(3.9)
3
> Math.trunc(-3.1)
-3
> Math.trunc(-3.9)
-3

Math.log10() 计算以 10 为底的对数

> Math.log10(100)
2

Math.hypot() 计算其参数平方和的平方根(勾股定理)

> Math.hypot(3, 4)
5    

5.2 新的整数字面量

ECMAScript 5 已经具有十六进制整数的字面量

> 0x9
9
> 0xA
10
> 0x10
16
> 0xFF
255

ECMAScript 6 带来了两种新的整数字面量

请记住,Number 方法 toString(radix) 可用于查看以 10 以外的进制表示的数字

> 255..toString(16)
'ff'
> 4..toString(2)
'100'
> 8..toString(8)
'10'

(双点是必需的,这样属性访问的点就不会与小数点混淆。)

5.2.1 八进制字面量的用例:Unix 风格的文件权限

在 Node.js 文件系统模块 中,有几个函数具有参数 mode。其值用于通过从 Unix 继承的编码来指定文件权限

这意味着权限可以用 9 位表示(3 类用户,每类 3 个权限)

  用户 所有
权限 r, w, x r, w, x r, w, x
8, 7, 6 5, 4, 3 2, 1, 0

单个用户类别的权限存储在 3 位中

权限 八进制数字
000 ––– 0
001 ––x 1
010 –w– 2
011 –wx 3
100 r–– 4
101 r–x 5
110 rw– 6
111 rwx 7

这意味着八进制数是所有权限的紧凑表示形式,您只需要 3 位数字,每类用户一位数字。两个例子

5.2.2 Number.parseInt() 和新的整数字面量

Number.parseInt()(与全局函数 parseInt() 相同)具有以下签名

Number.parseInt(string, radix?)
5.2.2.1 Number.parseInt():十六进制数字字面量

如果满足以下条件,则 Number.parseInt() 为十六进制字面量表示法提供特殊支持 – 如果

例如

> Number.parseInt('0xFF')
255
> Number.parseInt('0xFF', 0)
255
> Number.parseInt('0xFF', 16)
255

在所有其他情况下,仅解析数字直到第一个非数字字符

> Number.parseInt('0xFF', 10)
0
> Number.parseInt('0xFF', 17)
0
5.2.2.2 Number.parseInt():二进制和八进制数字字面量

但是,Number.parseInt() 不支持二进制或八进制字面量!

> Number.parseInt('0b111')
0
> Number.parseInt('0b111', 2)
0
> Number.parseInt('111', 2)
7

> Number.parseInt('0o10')
0
> Number.parseInt('0o10', 8)
0
> Number.parseInt('10', 8)
8

如果要解析这类字面量,则需要使用 Number()

> Number('0b111')
7
> Number('0o10')
8

只要没有特殊前缀并提供了参数 radixNumber.parseInt() 就可以很好地处理具有不同进制的数字

> Number.parseInt('111', 2)
7
> Number.parseInt('10', 8)
8

5.3 新的静态 Number 属性

本节介绍构造函数 Number 在 ECMAScript 6 中获得的新属性。

5.3.1 之前的全局函数

四个与数字相关的函数已经作为全局函数可用,并已作为方法添加到 Number 中:isFiniteisNaNparseFloatparseInt。它们的工作方式与其全局函数几乎相同,但 isFiniteisNaN 不再将其参数强制转换为数字,这对 isNaN 尤为重要。以下小节将解释所有详细信息。

5.3.1.1 Number.isFinite(number)

Number.isFinite(number) 确定 number 是否为实际数字(既不是 Infinity 也不是 -Infinity 也不是 NaN

> Number.isFinite(Infinity)
false
> Number.isFinite(-Infinity)
false
> Number.isFinite(NaN)
false
> Number.isFinite(123)
true

此方法的优点是它不会将其参数强制转换为数字(而全局函数会)

> Number.isFinite('123')
false
> isFinite('123')
true
5.3.1.2 Number.isNaN(number)

Number.isNaN(number) 检查 number 是否为 NaN 值。

ES5 中进行此检查的一种方法是通过 !==

> const x = NaN;
> x !== x
true

更具描述性的方法是通过全局函数 isNaN()

> const x = NaN;
> isNaN(x)
true

但是,此函数会将非数字强制转换为数字,如果结果为 NaN,则返回 true(这通常不是您想要的)

> isNaN('???')
true

新方法 Number.isNaN() 不会出现此问题,因为它不会将其参数强制转换为数字

> Number.isNaN('???')
false
5.3.1.3 Number.parseFloatNumber.parseInt

以下两种方法的工作方式与其同名的全局函数完全相同。为了完整起见,将它们添加到了 Number 中;现在所有与数字相关的函数都可以在那里使用。

5.3.2 Number.EPSILON

特别是在使用小数时,舍入误差在 JavaScript 中可能会成为一个问题3。例如,0.1 和 0.2 不能精确表示,如果您将它们相加并与 0.3(也不能精确表示)进行比较,您会注意到这一点。

> 0.1 + 0.2 === 0.3
false

Number.EPSILON 指定了比较浮点数时的合理误差范围。它提供了一种更好的比较浮点值的方法,如以下函数所示。

function epsEqu(x, y) {
    return Math.abs(x - y) < Number.EPSILON;
}
console.log(epsEqu(0.1+0.2, 0.3)); // true

5.3.3 Number.isInteger(number)

JavaScript 只有浮点数(双精度)。因此,整数只是没有小数部分的浮点数。

如果 number 是数字并且没有小数部分,则 Number.isInteger(number) 返回 true

> Number.isInteger(-17)
true
> Number.isInteger(33)
true
> Number.isInteger(33.1)
false
> Number.isInteger('33')
false
> Number.isInteger(NaN)
false
> Number.isInteger(Infinity)
false

5.3.4 安全整数

JavaScript 数字只有足够的存储空间来表示 53 位有符号整数。也就是说,范围为 −253 < *i* < 253 的整数 *i* 是*安全*的。稍后将解释这到底意味着什么。以下属性有助于确定 JavaScript 整数是否安全

*安全整数*的概念围绕着如何在 JavaScript 中表示数学整数。在 (−253, 253) 范围内(不包括上下限),JavaScript 整数是*安全*的:它们与它们表示的数学整数之间存在一对一的映射。

超出此范围,JavaScript 整数是*不安全*的:两个或多个数学整数表示为同一个 JavaScript 整数。例如,从 253 开始,JavaScript 只能表示每隔一个数学整数

> Math.pow(2, 53)
9007199254740992

> 9007199254740992
9007199254740992
> 9007199254740993
9007199254740992
> 9007199254740994
9007199254740994
> 9007199254740995
9007199254740996
> 9007199254740996
9007199254740996
> 9007199254740997
9007199254740996

因此,安全的 JavaScript 整数是明确表示单个数学整数的整数。

指定安全整数上下限的两个静态 Number 属性可以定义如下

Number.MAX_SAFE_INTEGER = Math.pow(2, 53)-1;
Number.MIN_SAFE_INTEGER = -Number.MAX_SAFE_INTEGER;

Number.isSafeInteger() 确定 JavaScript 数字是否是安全整数,可以定义如下

Number.isSafeInteger = function (n) {
    return (typeof n === 'number' &&
        Math.round(n) === n &&
        Number.MIN_SAFE_INTEGER <= n &&
        n <= Number.MAX_SAFE_INTEGER);
}

对于给定值 n,此函数首先检查 n 是否为数字和整数。如果两项检查都成功,则如果 n 大于或等于 MIN_SAFE_INTEGER 且小于或等于 MAX_SAFE_INTEGER,则 n 是安全的。

5.3.4.2 何时使用整数的计算是正确的?

我们如何确保使用整数的计算结果是正确的?例如,以下结果显然是不正确的

> 9007199254740990 + 3
9007199254740992

我们有两个安全的操作数,但结果不安全

> Number.isSafeInteger(9007199254740990)
true
> Number.isSafeInteger(3)
true
> Number.isSafeInteger(9007199254740992)
false

以下结果也不正确

> 9007199254740995 - 10
9007199254740986

这次,结果是安全的,但其中一个操作数不是

> Number.isSafeInteger(9007199254740995)
false
> Number.isSafeInteger(10)
true
> Number.isSafeInteger(9007199254740986)
true

因此,仅当所有操作数和结果都是安全的情况下,应用整数运算符 op 的结果才能保证是正确的。更正式地说

isSafeInteger(a) && isSafeInteger(b) && isSafeInteger(a op b)

意味着 a op b 是正确的结果。

5.4 新的 Math 功能

全局对象 Math 在 ECMAScript 6 中有几个新方法。

5.4.1 各种数值功能

5.4.1.1 Math.sign(x)

Math.sign(x) 返回

例子

> Math.sign(-8)
-1
> Math.sign(3)
1

> Math.sign(0)
0
> Math.sign(NaN)
NaN

> Math.sign(-Infinity)
-1
> Math.sign(Infinity)
1
5.4.1.2 Math.trunc(x)

Math.trunc(x) 去除 x 的小数部分。 补充了其他舍入方法 Math.floor()Math.ceil()Math.round()

> Math.trunc(3.1)
3
> Math.trunc(3.9)
3
> Math.trunc(-3.1)
-3
> Math.trunc(-3.9)
-3

您可以像这样实现 Math.trunc()

function trunc(x) {
    return Math.sign(x) * Math.floor(Math.abs(x));
}
5.4.1.3 Math.cbrt(x)

Math.cbrt(x) 返回 x 的立方根 (∛x)。

> Math.cbrt(8)
2

5.4.2 在指数和对数中使用 0 代替 1

如果一个小数位于零之后,则可以更精确地表示它。我将用十进制小数来演示这一点(JavaScript 的数字在内部以 2 为基数存储,但同样的推理也适用)。

以 10 为底的浮点数在内部表示为 *尾数* × 10指数。*尾数* 在小数点前有一位数字,*指数* 根据需要“移动”小数点。这意味着,如果将一个小数转换为内部表示形式,则点前的零会导致尾数小于点前的一。例如

就精度而言,这里重要的量是用有效数字衡量的尾数的容量。这就是为什么 (A) 比 (B) 提供更高的精度。

此外,JavaScript 以更高的精度表示接近零的数字(例如,小数)。

5.4.2.1 Math.expm1(x)

Math.expm1(x) 返回 Math.exp(x)-1Math.log1p() 的反函数。

因此,每当 Math.exp() 的结果接近 1 时,此方法都提供更高的精度。您可以在以下交互中看到两者之间的区别

> Math.expm1(1e-10)
1.00000000005e-10
> Math.exp(1e-10)-1
1.000000082740371e-10

前者是更好的结果,您可以使用任意精度浮点数(“bigfloats”)的库(例如 decimal.js)来验证

> var Decimal = require('decimal.js').config({precision:50});
> new Decimal(1e-10).exp().minus(1).toString()
'1.000000000050000000001666666666708333333e-10'
5.4.2.2 Math.log1p(x)

Math.log1p(x) 返回 Math.log(1 + x)Math.expm1() 的反函数。

因此,此方法允许您以更高的精度指定接近 1 的参数。以下示例演示了原因。

以下两次调用 log() 产生相同的结果

> Math.log(1 + 1e-16)
0
> Math.log(1 + 0)
0

相反,log1p() 产生不同的结果

> Math.log1p(1e-16)
1e-16
> Math.log1p(0)
0

Math.log1p() 精度更高的原因是 1 + 1e-16 的正确结果比 1e-16 具有更多有效数字

> 1 + 1e-16 === 1
true
> 1e-16 === 0
false

5.4.3 以 2 和 10 为底的对数

5.4.3.1 Math.log2(x)

Math.log2(x) 计算以 2 为底的对数。

> Math.log2(8)
3
5.4.3.2 Math.log10(x)

Math.log10(x) 计算以 10 为底的对数。

> Math.log10(100)
2

5.4.4 支持编译为 JavaScript

Emscripten 开创了一种后来被 asm.js 采用的编码风格:虚拟机(想想字节码)的操作在 JavaScript 的静态子集中表达。该子集可以由 JavaScript 引擎有效执行:如果它是 C++ 编译的结果,则其运行速度约为本机速度的 70%。

以下 Math 方法主要是为了支持 asm.js 和类似的编译策略而添加的,它们对其他应用程序没有那么有用。

5.4.4.1 Math.fround(x)

Math.fround(x)x 舍入为 32 位浮点值 (float)。由 asm.js 使用,用于告诉引擎在内部使用 float 值。

5.4.4.2 Math.imul(x, y)

Math.imul(x, y) 将两个 32 位整数 xy 相乘,并返回结果的低 32 位。这是唯一不能通过使用 JavaScript 运算符并将结果强制转换回 32 位来模拟的 32 位基本数学运算。例如,idiv 可以实现如下

function idiv(x, y) {
    return (x / y) | 0;
}

相反,将两个大的 32 位整数相乘可能会产生一个非常大的双精度数,从而丢失低位。

5.4.5 按位运算

为什么这很有趣?引用 Miro Samek 的“快速、确定和可移植的计数前导零

计算整数中的前导零是许多 DSP 算法中的关键操作,例如声音或视频处理中样本的归一化,以及实时调度程序中快速找到准备运行的最高优先级任务。

5.4.6 三角函数方法

5.5 常见问题解答:数字

5.5.1 如何使用超出 JavaScript 53 位范围的整数?

JavaScript 的整数范围为 53 位。每当需要 64 位整数时,这都是一个问题。例如:在其 JSON API 中,当推文 ID 过大时,Twitter 不得不从整数切换到字符串。

目前,解决此限制的唯一方法是使用更高精度数字(bigint 或 bigfloat)的库。一个这样的库是 decimal.js

在 JavaScript 中支持更大整数的计划已经存在,但可能需要一段时间才能实现。

下一页:6. 新字符串功能