JavaScript中的数值

发布时间 2023-08-21 11:48:47作者: 晓风晓浪

JavaScript中的主要数值类型是Number类型,用于表示整数和近似的实数。JavaScript采用了由IEEE 754标准定义的64位浮点格式来表示数值。这意味着JavaScript可以表示最大整数±1.797 693 134 862 315 7 × 10^308和最小整数±5 × 10^-324。

JavaScript中的这种数值格式允许我们准确表示介于-9,007,199,254,740,992(-253)和9,007,199,254,740,992(253)之间的所有整数。如果您的值超出此范围,可能会在最低有效位处丧失一些精度。

当一个数值直接出现在JavaScript程序中时,它被称为“数值字面量”。请注意,任何数值字面量都可以在前面加上减号(-)以使值为负。

1. 整数字面量.
在JavaScript程序中,十进制整数可以直接写为数字序列。例如:

0
3
10000000

除了十进制整数字面量,JavaScript还支持十六进制(基数16)的值。十六进制字面量以0x0X开头,后面跟着十六进制数字字符串。十六进制数字由数字0到9和字母a(或A)到f(或F)组成,其中a到f代表10到15。以下是十六进制整数字面量的示例:

//=> 255:(15*16 + 15)
0xff
//=> 195939070
0xBADCAFE

在ES6及更高版本中,您还可以使用前缀0b0o(或0B0O)来表示二进制(基数2)或八进制(基数8)的整数:

//=> 21:(1*16 + 0*8 + 1*4 + 0*2 + 1*1)
0b10101
//=> 255:(3*64 + 7*8 + 7*1)
0o377

(程序员的软技能:ke.qq.com/course/6034346)

2. 浮点数字面量.
浮点数字面量可以包含小数点,并遵循实数的传统语法。实数由数字的整数部分、小数点和数字的小数部分组成。

浮点数字面量还可以使用指数表示法,其中实数后跟字母e(或E),一个可选的加号或减号,以及一个整数指数。这种表示法表示实数乘以10的指数次幂。

更简洁的语法形式为:

[digits][.digits][(E|e)[(+|-)]digits]

例如:

3.14
2345.6789
.333333333333333333
//6.02 x 10^23
6.02e23
//1.4738223 x 10^-32
1.4738223E-32

您可以使用下划线来分隔数值字面量的各个部分,这是一种已进入标准化后期并受到所有主要浏览器和Node实现支持的功能:

// 使用下划线作为千分位分隔符
let billion = 1_000_000_000;
// 使用下划线作为字节分隔符
let bytes = 0x89_AB_CD_EF;
// 使用下划线作为半字节分隔符
let bits = 0b0001_1101_0111;
// 也可以用于小数部分
let fraction = 0.123_456_789;

(程序员的软技能:ke.qq.com/course/6034346)

3. JavaScript中的算术运算。

JavaScript程序使用语言提供的算术运算符来操作数值。这些运算符包括加法+、减法-、乘法*、除法/和取模(除法后的余数)%。ES2016引入了指数运算符**

除了这些基本的算术运算符,JavaScript还通过Math对象提供了一组函数和常量,以支持更复杂的数学计算:

// => 9007199254740992:2的53次方
Math.pow(2, 53)
// => 1.0:四舍五入到最近的整数
Math.round(0.6)
// => 1.0:向上取整到最近的整数
Math.ceil(0.6)
// => 0.0:向下取整到最近的整数
Math.floor(0.6)
// => 5:绝对值
Math.abs(-5)
// 返回提供参数的最大值
Math.max(x, y, z)
// 返回提供参数的最小值
Math.min(x, y, z)
// 伪随机数x,其中0 <= x < 1.0
Math.random()
// π:代表数学常数π的常量
Math.PI
// e:代表自然对数的底数e的常量
Math.E
// => 3**0.5:3的平方根
Math.sqrt(3)
// => 3**(1/3):3的立方根
Math.pow(3, 1/3)
// 三角函数:还有Math.cos、Math.atan等
Math.sin(0)
// 10的自然对数
Math.log(10)
// 以10为底的100的对数
Math.log(100) / Math.LN10
// 以2为底的512的对数
Math.log(512) / Math.LN2
// Math.E的立方
Math.exp(3)

ES6在Math对象上引入了一组新的函数:

// => 3:立方根
Math.cbrt(27)
// => 5:一组参数平方和的平方根
Math.hypot(3, 4)
// => 2:以10为底的对数
Math.log10(100)
// => 10:以2为底的对数
Math.log2(1024)
// (1+x)的自然对数:对于非常小的x很精确
Math.log1p(x)
// Math.exp(x) - 1;Math.log1p()的反函数
Math.expm1(x)
// 根据参数的符号返回-1、0或1
Math.sign(x)
// => 6:优化的32位整数乘法
Math.imul(2, 3)
// => 28:32位二进制表示中前导零位的数量
Math.clz32(0xf)
// => 3:去掉小数部分以得到整数
Math.trunc(3.9)
// 四舍五入到最近的32位浮点值
Math.fround(x)
// 双曲正弦,还有Math.cosh()和Math.tanh()
Math.sinh(x)
// 双曲反正弦,还有Math.acosh()和Math.atanh()
Math.asinh(x)

在JavaScript中,算术不会在遇到溢出、下溢或除以零时抛出错误。当数值操作的结果超出可表示范围(溢出)时,结果是特殊的无限值Infinity。类似地,当负数的绝对值超出负数的可表示范围(下溢)时,结果为负无穷-Infinity。这两个无限值的行为符合预期:涉及加法、减法、乘法或除法的任何算术运算都会导致无限值(尽管符号可能会改变)。

当数值操作的结果接近零但仍大于最小可表示数时,发生下溢。在这种情况下,JavaScript返回0。如果下溢发生在倒数的情况下,JavaScript返回一个称为“负零”的特殊值。这个值与普通零几乎无法区分,对于JavaScript程序员来说很少会引起关注。

在JavaScript中,除以零不是错误;它只会导致无限或负无穷。然而,有一个例外:将0除以0会得到一个称为“非数”(NaN)的特殊值。此外,类似无限除以无限、负数的平方根或在算术运算中使用NaN作为操作数等操作都会产生NaN作为结果。

JavaScript预定义了全局常量InfinityNaN,分别对应正无穷大和非数。这些值也可以通过Number对象的属性访问:

// 由于过大无法表示,得到正无穷大
Infinity
// 与上面相同
Number.POSITIVE_INFINITY
// => Infinity
1 / 0
// => Infinity;溢出
Number.MAX_VALUE * 2
// 由于过大无法表示为负数,得到负无穷大
-Infinity
// 与上面相同
Number.NEGATIVE_INFINITY
// => -Infinity
-1 / 0
// => -Infinity
-Number.MAX_VALUE * 2

// NaN:非数
NaN
// 与上面相同,不同的语法
Number.NaN
// => NaN
0 / 0
// => NaN
Infinity / Infinity

// => 0:下溢
Number.MIN_VALUE / 2
// => -0:负零
-Number.MIN_VALUE / 2
// => -0:也是负零
-1 / Infinity
//
-0

ES6在Number对象上引入了以下属性:

// 与全局parseInt()函数相同
Number.parseInt()
// 与全局parseFloat()函数相同
Number.parseFloat()
// 检查x是否为NaN
Number.isNaN(x)
// 检查x是否为有限数
Number.isFinite(x)
// 检查x是否为整数
Number.isInteger(x)
//
Number.isSafeInteger(x)
// => -(2**53 - 1)
Number.MIN_SAFE_INTEGER
// => 2**53 - 1
Number.MAX_SAFE_INTEGER
// => 2**-52:两个可表示数之间的最小差值
Number.EPSILON

在JavaScript中,NaN具有一种不寻常的特性:它既不等于任何值,也不等于自身。这意味着您不能通过使用x === NaN比较来确定变量x是否为NaN。相反,您必须使用x != xNumber.isNaN(x)来测试NaN。这两个表达式只在x与全局常量NaN具有相同值时才返回true

全局函数isNaN()Number.isNaN()类似。当参数为NaN或参数为不能转换为数字的非数值时,它返回true。相关的函数Number.isFinite()在参数不是NaN、Infinity或-Infinity时返回true。全局函数isFinite()在参数为有限数或可转换为有限数的值时返回true

负零具有稍微不寻常的行为。它被认为等于正零(即使在JavaScript中使用严格相等比较),这意味着除了作为除数的角色外,很难区分这两个值:

// 普通零
let zero = 0;
// 负零
let negz = -0;
// => true:零等于负零
zero === negz
// => false:Infinity不等于负无穷
1 / zero === 1 / negz

(程序员的软技能:ke.qq.com/course/6034346)

4. 二进制浮点数和舍入误差。

实数是无限多的,但JavaScript的浮点数格式只能表示其中的有限子集(确切地说,它可以表示多达18,437,736,874,454,810,627个不同的值)。这意味着当您在JavaScript中使用实数时,您操作的数值通常是实际值的近似值。

JavaScript(以及所有现代编程语言)使用IEEE-754浮点表示,这是一种二进制表示。这种表示可以准确地表示像1/2、1/8和1/1024这样的分数。然而,我们常用的最常见的分数(尤其是在财务计算中)是十进制分数,如1/10、1/100等。二进制浮点表示无法精确表示甚至简单的数字如0.1。

虽然JavaScript的数值具有足够的精度,可以非常接近地近似0.1,但不能完全表示它。这可能会导致问题,如以下代码所示:

// 30美分减去20美分
let x = 0.3 - 0.2;
// 20美分减去10美分
let y = 0.2 - 0.1;
// => false:这两个值不相等!
x === y
// => false:.3 - .2不等于.1
x === 0.1
// => true:.2 - .1等于.1
y === 0.1

由于舍入误差,近似值0.3和0.2之间的差异不等于近似值0.2和0.1之间的差异。这不是JavaScript特定的问题;这是所有使用二进制浮点数的编程语言的常见问题。还要注意,上述代码中的xy的值非常接近,它们非常接近正确的值。计算出的值对于大多数目的来说都是完全合适的,但不建议尝试比较它们的精确相等性。

如果近似浮点值在您的程序中造成问题,您可以考虑使用等价的整数。例如,在处理与货币相关的计算时,您可以使用以分为单位的整数表示,而不是以美元的一部分为单位的分数。
(程序员的软技能:ke.qq.com/course/6034346)

5. 用BigInt表示任意精度整数。

在ES2020中,JavaScript引入了一种新的数字类型,称为BigInt。截至2020年初,Chrome、Firefox、Edge和Node.js已经实现了这种类型,Safari也在进行相关工作。正如名称所示,BigInt值是整数。添加此类型的主要动机是为了表示64位整数,这对于与许多其他语言和API的兼容性至关重要。然而,BigInt值可以具有数千甚至数百万位的数字,以满足大数值的需求(尽管BigInt实现不适合用于加密,因为它们不考虑防御定时攻击)。

BigInt字面量由一系列数字后跟小写字母"n"组成。默认情况下,基数为10,但您可以使用前缀0b、0o和0x分别表示二进制、八进制和十六进制的BigInt:

// 一个不太大的BigInt字面量
1234n
// 二进制BigInt
0b111111n
// 八进制BigInt
0o7777n
// => 2n ** 63n:一个64位整数
0x8000000000000000n

您可以使用BigInt()函数将常规JavaScript数字或字符串转换为BigInt值:

// => 9007199254740991n
BigInt(Number.MAX_SAFE_INTEGER)
// 一个由1后面跟100个零组成的数字
let string = "1" + "0".repeat(100);
// => 10n ** 100n:一个天文数字
BigInt(string)

与BigInt值的算术操作类似于常规JavaScript数字操作,除法除去余数并朝向零四舍五入:

// => 3000n
1000n + 2000n
// => 1000n
3000n - 2000n
// => 6000000n
2000n * 3000n
// => 3n:商为3
3000n / 997n
// => 9n:余数为9
3000n % 997n
// 一个有39457位的梅森素数
(2n ** 131071n) - 1n

尽管标准的+、-、*、/、%和**运算符适用于BigInt,但您不能将BigInt操作数与常规数字操作数混合使用。乍看起来,这个规则可能有点奇怪,但实际上是合理的。如果一个数值类型比另一个更通用,那么定义混合操作数计算并返回更通用的类型是很容易的。然而,在这种情况下,这两种类型都不比另一种更通用:BigInt可以表示非常大的值,使其在常规数值类型之间更通用。但是BigInt只能表示整数,这使得常规JavaScript数值类型在这个方面更通用。这种困境无法解决,因此在使用算术运算符时,JavaScript简单地禁止混合使用这两种操作数类型。

另一方面,比较运算符允许混合操作数类型:

// => true
1 < 2n
// => true
2 > 1n
// => true
0 == 0n
// => false:===还检查类型相等性
0 === 0n

位运算符通常可以与BigInt操作数一起使用。然而,Math对象的函数中没有一个接受BigInt操作数。
(程序员的软技能:ke.qq.com/course/6034346)

6. 日期和时间。

JavaScript提供了一个简单的Date类,用于表示和操作与日期和时间有关的数据。JavaScript的Date是一个对象,但它也具有一个数值表示,称为时间戳,它是自1970年1月1日以来的毫秒数:

// 当前时间的时间戳(数值)
let timestamp = Date.now();
// 当前时间的Date对象
let now = new Date();
// 转换为毫秒时间戳
let ms = now.getTime();
// 转换为ISO格式的字符串
let iso = now.toISOString();

(程序员的软技能:ke.qq.com/course/6034346)