在 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 标准中的相等比较算法
至此,关于数字类型就要告一段落了,希望大家在本小节中能有所收获!
欢迎大家点赞收藏关注!!!
同时欢迎大家关注我的微信公众号:火锅只爱鸳鸯锅 !