在 JavaScript 中,数字有两种类型:

(1)常规数字类型,以 64 位的格式 IEEE-754 存储,也被称为“双精度浮点数”。这也是我们绝大多数时候使用的数字。

(2)BigInt 数字,用于表示任意长度的整数。有时会需要它们,因为常规数字不能安全地超过 2^53 或小于 -2^53。

1、常规数字类型

一般情况下我们书写数字类型不会很大,就像这样:

let age = 24;
let score = 100;



但是如果我们要表示 10 亿呢?实际上有多种写法:

(1)普通写法

let billion = 1000000000;

(2)使用下划线 _ 作为分隔符

let billion = 1_000_000_000; // 1000000000

这里的下划线类似于语法糖,使数字具有更强的可读性。JavaScript 引擎会直接忽略数字之间的下划线。

(3)附加字母 “e” 并指定零的个数

let billion = 1e9;  // 10 亿,表示:数字 1 后面跟 9 个 0

可以认为是:e 把数字乘以 1 后面跟着给定数量的 0 的数字。

这里e后面也可以加上一个 + 号,不加也行。

当然了,小数也可以这样表达,就像这样:

let num = 0.0000001;
let num = 1e-7; // 1 的左边有 7 个 0

注意:e不区分大小写。



2、BigInt 数字

BigInt 是一种特殊的数字类型,它提供了对任意长度整数的支持。

创建 bigint 的方式有两种:
(1)在一个整数字面量后面加 n

let bigint = 1234567890123456789012345678901234567890n;

(2)调用 BigInt 函数

let bigint1 = BigInt("1234567890123456789012345678901234567890");
let bigint2 = BigInt(10000); // 等同于 10000n

BigInt 可以将数字形的字符串或者常规数字类型转为 BigInt 格式

关于 bigint 的运算方法就不在这里展开讲解了。实际上在日常的工作中极少会使用到该类型,大家仅需要做一个了解,知道有这种类型即可。



3、数字进制

在 JavaScript 中,最常用的当然是十进制了。但是这里还有几种进制需要大家了解一下。

(1)十六进制

十六进制一般被用于表示颜色,编码字符等,有一种简单的写法:0x + 数字。就像这样:

console.log( 0x00 ); // 0
console.log( 0xff ); // 255
console.log( 0xFF ); // 255 (不区分大小写)

(2)二进制和八进制

二进制和八进制在 javascript 中使用不多,但也支持使用 0b 和 0o 前缀。下面给几个简单的例子:

let a = 0b11111111; // 二进制表示的 255
let b = 0o377; // 八进制表示的 255
console.log( a === b ); true

(3)进制转换

我们可以用 js 为我们提供的 toString 方法来实现进制转换。

看看这个例子:

let num = 255;

console.log( num.toString(16) );  // ff
console.log( num.toString(2) );   // 11111111
console.log( num ); // 255(不会改变原数)

从上面的例子可以看出,toString 的使用很简单,只需要传入你需要的进制(范围:2-36),即可完成进制的转换。

这里有个小技巧,即我们可以直接在数字上调用方法,可以像下面这样:

console.log( 255..toString(16) ); // ff
console.log( 255..toString(2) );  // 11111111

可以看到,在数字后面加了两个小点并不是因为写错了,是因为如果我们想直接在一个数字上调用一个方法,比如上面例子中的 toString,那么我们需要在它后面放置两个点 …。



4、数字操作

对小数,负数的操作也是数字类型中比较重要的一点。

JavaScript Math 对象为我们提供了很多方便的方法。我们在这里拿一些来举例。

(1)Math.floor:向下舍入

console.log( Math.floor( 3.1 ) ); // 3
console.log( Math.floor( -1.1 ) ); // 2

(2)Math.ceil:向上舍入

console.log( Math.ceil( 3.1 ) ); // 4
console.log( Math.ceil( -1.1 ) ); // -1

(3)Math.round:四舍五入

console.log( Math.round( 3.1 ) ); // 3
console.log( Math.round( 3.6 ) ); // 4
console.log( Math.round( 3.5 ) ); // 4

(4)Math.max,Math.min:返回最大最小值

console.log( Math.max(5,10) );
console.log( Math.min(5,10) );

(5)Math.abs:返回绝对值

console.log( Math.abs(-100) ); // 100

当然了,Math 的全部方法不止这些,以上的仅仅是常用的一些,更多的方法同学们可以查阅 Math 对象



5、两个特殊的值

在数字类型中,有两个特殊的数值,它们属于 number 类型,但不是“普通”数字。

(1)Infinity

Infinity 是表示正无穷大的数值。
-Infinity 是表示负无穷大的数值。

当数超过浮点数的上限时,即 1.797693134862315E+308,显示 Infinity。
当数超过浮点数的下限时,即 -1.797693134862316E+308,显示 -Infinity。

console.log(1.797693134862315E+308); // 1.797693134862315e+308
console.log(1.797693134862315E+309); // Infinity

console.log(-1.797693134862315E+308); // -1.797693134862315e+308
console.log(-1.797693134862315E+309); // -Infinity

那么如何检测呢?可以用 isFinite 函数, 该函数用于检查其参数是否是无穷大,也可以理解为是否为一个有限数值。

如果参数是 NaN,正无穷大或者负无穷大,会返回 false,其他返回 true。就像这样:

console.log( isFinite("15") ); // true
console.log( isFinite("str") ); // false,因为是一个特殊的值:NaN
console.log( isFinite(Infinity) ); // false,因为是一个特殊的值:Infinity

isFinite(value) 会将其参数转换为数字,因此有时该方法被用于验证字符串值是否为常规数字。

小tip:在所有数字函数中,包括 isFinite,空字符串或仅有空格的字符串均被视为 0。

(2)NaN

NaN 即非数值(Not a Number),NaN 属性用于引用特殊的非数字值,该属性指定的并不是不合法的数字。

值 “NaN” 是独一无二的,它不等于任何东西,包括它自身。

console.log( NaN === NaN ); // false

那么如何检测某个值是不是 NaN 呢?

一是可以使用 js 提供的 isNaN 方法:

console.log( isNaN(NaN) ); // true
console.log( isNaN("str") ); // true

二是可以使用一个特殊的内建方法 Object.is :

consoloe.log( Object.is(NaN,NaN) ); // true

对 ES 标准中相等比较算法感兴趣的同学可以查看该文章: ES 标准中的相等比较算法



6、parseInt 和 parseFloat 的使用

在一般的数字类型转换中,使用加号 + 或 Number() 的数字转换是严格的。但是如果一个值不完全是一个数字则会失败,就像这样:

console.log( +"100px" ); // NaN
console.log( Number("100px") ); // NaN

唯一的例外是字符串开头或结尾的空格,因为它们会被忽略。像这样:

console.log( +" 100 " ); // 100
console.log( Number(" 100 ") ); // 100

为了从字符串中直接“读取”数字,parseInt 和 parseFloat 由此出现。并且如果发生错误,则返回收集到的数字。函数 parseInt 返回一个整数,而 parseFloat 返回一个浮点数:

console.log( parseInt('100px') ); // 100
console.log( parseFloat('12.5em') ); // 12.5

// 打印 12,只有整数部分被返回了
console.log( parseInt('12.3') );

// 打印 12.3,正确情况只存在一个小数点,因此在第二个点处停止了读取
console.log( parseFloat('12.3.4') );

在没有数字可读时会出现返回 NaN 的情况:

console.log( parseInt('abc' )); // NaN
console.log( parseInt('a100' )); // NaN



注意,parseInt() 函数具有可选的第二个参数。它指定了数字系统的基数,因此 parseInt 还可以解析十六进制数字、二进制数字等的字符串:

console.log( parseInt('0xff', 16) ); // 255
console.log( parseInt('ff', 16) ); // 255,没有 0x 仍然有效

console.log( parseInt('2n9c', 36) ); // 123456

需要明确的是,parseInt 是把其他进制转为十进制,这是无法改变的。因此第二个参数是表明当前第一个参数对应的进制。

如果你需要从十进制转到其他进制,不妨回忆下我们上面提到数字类型自带的 toString 方法。



其实到这里,对数字类型的掌握已经差不多了。但既然是从头再学系列,那就要学一些新的东西吧。下面让我们看看一个有趣的小节。



7、不精确计算

在 JavaScript 中,数字是以 64 位格式 IEEE-754 ( IEEE二进制浮点数算术标准 ) 表示的。

所以我们可以用 64 位二进制来存储一个数字:
(1)52 位用于存储数字;
(2)11 位用于存储小数点的位置(如果是整数则这 11 位全为为0),
(3)1 位用于符号。

如果数字太大,则会导致出现我们上面提到的无穷大。

console.log(1.797693134862315E+309); // Infinity



关于无穷大的出现其实并不会经常出现,因为实际中一般用不大这么大的数。但是有一种情况却经常发生,那就是精度的损失。

看看这个经典的例子:

console.log( 0.1 + 0.2 == 0.3 );

如果开始不了解的同学,一定会认为这里打印的结果是 true 吧。然后事实并非如此。来看看:

console.log( 0.1 + 0.2 ); // 0.30000000000000004

这下你应该能知道打印结果为什么是 false 了。但是新的问题却更加严重,就是为什么 0.1 + 0.2 的结果是 0.30000000000000004。



在 JS 中,数字是以二进制的形式存储在内存中的,即一个 1 和 0 的序列。但是你需要明白的是:

在十进制数字系统中看起来很简单的 0.1,0.2 这样的小数,实际上在二进制形式中是无限循环的小数。

就好比在十进制中,1 / 3 的结果是 0.33333… 无限循环。

在十进制的数字系统中,以 10 的整数次幂作为除数能够正常工作,但是以 3 作为除数则不能。

也是同样的原因,在二进制的数字系统中,以 2 的整数次幂作为除数时能够正常工作,但 1/10 就变成了一个无限循环的二进制小数。

使用二进制数字系统无法精确的存储 0.1 或 0.2,就像没有办法将三分之一存储为十进制小数一样。



为了解决这个问题,IEEE-754 数字格式通过将数字舍入到最接近的可能数字来解决此问题。因此在表面我们看不到这些“极小的精度损失”,但是这个问题的确存在。

看看这个也许能更加直观:

console.log( 0.1.toFixed(20) ); // 0.10000000000000000555
console.log( 0.2.toFixed(20) ); // 0.20000000000000001110
console.log( (0.1 + 0.2).toFixed(20) ); // 0.30000000000000004441

console.log( 0.3.toFixed(20) ); // 0.29999999999999998890



这个问题不仅仅是 JavaScript 中才有,PHP,Java,C,Perl,Ruby 基于相同数字格式的编程语言也会出现同样的问题。

就目前的情况而言,最可靠的方法来解决该问题就是我们上面使用到的 toFixed 方法。

toFixed 总是返回字符串,若有必要可以进行类型转换。具体的转换方法在上面已经提到了。

tip:使用乘/除法可以减少误差,但不能完全消除误差

注意,在 JS 中,数字过大时使用parseInt、parseFloat、Number 等对“数字字符串”进行转换时也会造成精度丢失。



除了精度丢失,还存在一个有趣的数字现象:

console.log( +0 === -0 ); // true

有同学也许会说,这不是废话吗?但是你要知道,在 js 的数字表示的64位二进制中,有一位是用来表示符号的。那么按道理 +0 和 -0 应该是不同的。

再来看看这个例子:

console.log( Object.is(+0, -0) ); // false

因此,+0 和 -0 本质上在 js 中应该是不同的,但是在大多数情况下,这种区别并不明显,因为运算符将它们视为相同的值。

想了解更加详细的关于对比算法的知识,可以查阅: ES 标准中的相等比较算法




至此,关于数字类型就要告一段落了,希望大家在本小节中能有所收获!




欢迎大家点赞收藏关注!!!

同时欢迎大家关注我的微信公众号:火锅只爱鸳鸯锅 !