JavaScript 数据类型 —— number 类型详解

简介: JavaScript 数据类型 —— number 类型详解

640.png

数字类型


在现代 JavaScript 中,数字(number)有两种类型:


  1. JavaScript 中的常规数字以 64 位的格式 IEEE-754[2] 存储,也被称为“双精度浮点数”。这是我们大多数时候所使用的数字,我们将在本章中学习它们。
  2. BigInt 用于表示任意长度的整数。有时会需要它们,因为正如我们在前面的章节 数据类型[3] 中提到的,常规整数不能安全地超过 (253-1) 或小于 -(253-1)。由于仅在少数特殊领域才会用到 BigInt,因此我们在特殊的章节 BigInt[4] 中对其进行了介绍。


所以,在这里我们将讨论常规数字类型。现在让我们开始学习吧。


编写数字的更多方法


假如我们需要表示 10 亿。显然,我们可以这样写:


let billion = 1000000000;


我们也可以使用下划线 _ 作为分隔符:


let billion = 1_000_000_000;


这里的下划线 _ 扮演了“语法糖[5]”的角色,使得数字具有更强的可读性。JavaScript 引擎会直接忽略数字之间的 _,所以 上面两个例子其实是一样的。

但在现实生活中,我们通常会尽量避免写带一长串零的数。因为我们比较懒……我们会尝试将 10 亿写成 "1bn",或将 73 亿写成 "7.3bn"。对于大多数大的数字来说都是如此。


在 JavaScript 中,我们可以通过在数字后面附加字母 "e" 并指定零的个数来缩短数字:


let billion = 1e9;  // 10 亿,字面意思:数字 1 后面跟 9 个 0
alert( 7.3e9 );  // 73 亿(与 7300000000 和 7_300_000_000 相同)


换句话说,e 把数字乘以 1 后面跟着给定数量的 0 的数字。


1e3 === 1 * 1000; // e3 表示 *1000
1.23e6 === 1.23 * 1000000; // e6 表示 *1000000


现在让我们写一些非常小的数字。例如,1 微秒(百万分之一秒):


let mcs = 0.000001;


就像以前一样,可以使用 "e" 来完成。如果我们想避免显式地写零,我们可以这样写:


let mcs = 1e-6; // 1 的左边有 6 个 0


如果我们数一下 0.000001 中的 0 的个数,是 6 个。所以自然是 1e-6

换句话说,e 后面的负数表示除以 1 后面跟着给定数量的 0 的数字:


// -3 除以 1 后面跟着 3 个 0 的数字
1e-3 === 1 / 1000; // 0.001
// -6 除以 1 后面跟着 6 个 0 的数字
1.23e-6 === 1.23 / 1000000; // 0.00000123
// 一个更大一点的数字的示例
1234e-2 === 1234 / 100; // 12.34,小数点移动两次


十六进制,二进制和八进制数字


十六进制[6] 数字在 JavaScript 中被广泛用于表示颜色,编码字符以及其他许多东西。所以自然地,有一种较短的写方法:0x,然后是数字。


例如:


alert( 0xff ); // 255
alert( 0xFF ); // 255(一样,大小写没影响)

二进制和八进制数字系统很少使用,但也支持使用 0b0o 前缀:


let a = 0b11111111; // 二进制形式的 255
let b = 0o377; // 八进制形式的 255
alert( a == b ); // true,两边是相同的数字,都是 255

只有这三种进制支持这种写法。对于其他进制,我们应该使用函数 parseInt(我们将在本章后面看到)。


toString(base)


方法 num.toString(base) 返回在给定 base 进制数字系统中 num 的字符串表示形式。

举个例子:


let num = 255;
alert( num.toString(16) );  // ff
alert( num.toString(2) );   // 11111111


base 的范围可以从 236。默认情况下是 10

常见的用例如下:

  • base=16 用于十六进制颜色,字符编码等,数字可以是 0..9A..F
  • base=2 主要用于调试按位操作,数字可以是 01
  • base=36 是最大进制,数字可以是 0..9A..Z。所有拉丁字母都被用于了表示数字。对于 36 进制来说,一个有趣且有用的例子是,当我们需要将一个较长的数字标识符转换成较短的时候,例如做一个短的 URL。可以简单地使用基数为 36 的数字系统表示:


alert( 123456..toString(36) ); // 2n9c


使用两个点来调用一个方法

请注意 123456..toString(36) 中的两个点不是打错了。如果我们想直接在一个数字上调用一个方法,比如上面例子中的 toString,那么我们需要在它后面放置两个点 ..

如果我们放置一个点:123456.toString(36),那么就会出现一个 error,因为 JavaScript 语法隐含了第一个点之后的部分为小数部分。如果我们再放一个点,那么 JavaScript 就知道小数部分为空,现在使用该方法。

也可以写成 (123456).toString(36)


舍入


舍入(rounding)是使用数字时最常用的操作之一。

这里有几个对数字进行舍入的内建函数:


Math.floor:向下舍入:3.1 变成 3-1.1 变成 -2

Math.ceil:向上舍入:3.1 变成 4-1.1 变成 -1

Math.round:向最近的整数舍入:3.1 变成 33.6 变成 4,中间值 3.5 变成 4


Math.trunc(IE 浏览器不支持这个方法):移除小数点后的所有内容而没有舍入:3.1 变成 3-1.1 变成 -1


这个是总结它们之间差异的表格:



Math.floor Math.ceil Math.round Math.trunc
3.1 3 4 3 3
3.6 3 4 4 3
-1.1 -2 -1 -1 -1
-1.6 -2 -1 -2 -1


这些函数涵盖了处理数字小数部分的所有可能方法。但是,如果我们想将数字舍入到小数点后 n 位,该怎么办?


例如,我们有 1.2345,并且想把它舍入到小数点后两位,仅得到 1.23

有两种方式可以实现这个需求:


  1. 乘除法
    例如,要将数字舍入到小数点后两位,我们可以将数字乘以 100,调用舍入函数,然后再将其除回。


let num = 1.23456;
alert( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23
  1. 函数 toFixed(n)[7] 将数字舍入到小数点后 n 位,并以字符串形式返回结果。


let num = 12.34;
alert( num.toFixed(1) ); // "12.3"
  1. 这会向上或向下舍入到最接近的值,类似于 Math.round


let num = 12.36;
alert( num.toFixed(1) ); // "12.4"
  1. 请注意 toFixed 的结果是一个字符串。如果小数部分比所需要的短,则在结尾添加零:


let num = 12.34;
alert( num.toFixed(5) ); // "12.34000",在结尾添加了 0,以达到小数点后五位
  1. 我们可以使用一元加号或 Number() 调用,将其转换为数字,例如 + num.toFixed(5)


不精确的计算


在内部,数字是以 64 位格式 IEEE-754[8] 表示的,所以正好有 64 位可以存储一个数字:其中 52 位被用于存储这些数字,其中 11 位用于存储小数点的位置,而 1 位用于符号。


如果一个数字真的很大,则可能会溢出 64 位存储,变成一个特殊的数值 Infinity


alert( 1e500 ); // Infinity


这可能不那么明显,但经常会发生的是,精度的损失。


考虑下这个(falsy!)相等性测试:


alert( 0.1 + 0.2 == 0.3 ); // *!*false*/!*


没错,如果我们检查 0.10.2 的总和是否为 0.3,我们会得到 false

奇了怪了!如果不是 0.3,那能是啥?


alert( 0.1 + 0.2 ); // 0.30000000000000004


我擦!想象一下,你创建了一个电子购物网站,如果访问者将价格为 ¥ 0.10¥ 0.20 的商品放入了他的购物车。订单总额将是 ¥ 0.30000000000000004。这会让任何人感到惊讶。


但为什么会这样呢?


一个数字以其二进制的形式存储在内存中,一个 1 和 0 的序列。但是在十进制数字系统中看起来很简单的 0.10.2 这样的小数,实际上在二进制形式中是无限循环小数。

什么是 0.10.1 就是 1 除以 101/10,即十分之一。在十进制数字系统中,这样的数字表示起来很容易。将其与三分之一进行比较:1/3。三分之一变成了无限循环小数 0.33333(3)


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


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


IEEE-754 数字格式通过将数字舍入到最接近的可能数字来解决此问题。这些舍入规则通常不允许我们看到“极小的精度损失”,但是它确实存在。


我们可以看到:


alert( 0.1.toFixed(20) ); // 0.10000000000000000555


当我们对两个数字进行求和时,它们的“精度损失”会叠加起来。

这就是为什么 0.1 + 0.2 不等于 0.3


不仅仅是 JavaScript

许多其他编程语言也存在同样的问题。

PHP,Java,C,Perl,Ruby 给出的也是完全相同的结果,因为它们基于的是相同的数字格式。


我们能解决这个问题吗?当然,最可靠的方法是借助方法 toFixed(n)[9] 对结果进行舍入:


let sum = 0.1 + 0.2;
alert( sum.toFixed(2) ); // 0.30


请注意,toFixed 总是返回一个字符串。它确保小数点后有 2 位数字。如果我们有一个电子购物网站,并需要显示 ¥ 0.30,这实际上很方便。对于其他情况,我们可以使用一元加号将其强制转换为一个数字:


let sum = 0.1 + 0.2;
alert( +sum.toFixed(2) ); // "0.30"


我们可以将数字临时乘以 100(或更大的数字),将其转换为整数,进行数学运算,然后再除回。当我们使用整数进行数学运算时,误差会有所减少,但仍然可以在除法中得到:


alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3
alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001


因此,乘/除法可以减少误差,但不能完全消除误差。


有时候我们可以尝试完全避免小数。例如,我们正在创建一个电子购物网站,那么我们可以用角而不是元来存储价格。但是,如果我们要打 30% 的折扣呢?实际上,完全避免小数处理几乎是不可能的。只需要在必要时剪掉其“尾巴”来对其进行舍入即可。


有趣的事儿

尝试运行下面这段代码:


// Hello!我是一个会自我增加的数字!
alert( 9999999999999999 ); // 显示 10000000000000000


出现了同样的问题:精度损失。有 64 位来表示该数字,其中 52 位可用于存储数字,但这还不够。所以最不重要的数字就消失了。

JavaScript 不会在此类事件中触发 error。它会尽最大努力使数字符合所需的格式,但不幸的是,这种格式不够大到满足需求。


两个零

数字内部表示的另一个有趣结果是存在两个零:0-0

这是因为在存储时,使用一位来存储符号,因此对于包括零在内的任何数字,可以设置这一位或者不设置。

在大多数情况下,这种区别并不明显,因为运算符将它们视为相同的值。


测试:isFinite 和 isNaN


还记得这两个特殊的数值吗?

  • Infinity(和 -Infinity)是一个特殊的数值,比任何数值都大(小)。
  • NaN 代表一个 error。

它们属于 number 类型,但不是“普通”数字,因此,这里有用于检查它们的特殊函数:

  • isNaN(value) 将其参数转换为数字,然后测试它是否为 NaN


alert( isNaN(NaN) ); // true
alert( isNaN("str") ); // true
  • 但是我们需要这个函数吗?我们不能只使用 === NaN 比较吗?很不幸,这不行。值 "NaN" 是独一无二的,它不等于任何东西,包括它自身:


alert( NaN === NaN ); // false
  • isFinite(value) 将其参数转换为数字,如果是常规数字而不是 NaN/Infinity/-Infinity,则返回 true


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

有时 isFinite 被用于验证字符串值是否为常规数字:


let num = +prompt("Enter a number", '');
// 结果会是 true,除非你输入的是 Infinity、-Infinity 或不是数字
alert( isFinite(num) );

请注意,在所有数字函数中,包括 isFinite,空字符串或仅有空格的字符串均被视为 0


Object.is 进行比较

有一个特殊的内建方法 Object.is,它类似于 === 一样对值进行比较,但它对于两种边缘情况更可靠:

  1. 它适用于 NaNObject.is(NaN,NaN) === true,这是件好事。
  2. 0-0 是不同的:Object.is(0,-0) === false,从技术上讲这是对的,因为在内部,数字的符号位可能会不同,即使其他所有位均为零。

在所有其他情况下,Object.is(a,b)a === b 相同。

这种比较方式经常被用在 JavaScript 规范中。当内部算法需要比较两个值是否完全相同时,它使用 Object.is(内部称为 SameValue[10])。


parseInt 和 parseFloat


使用加号 +Number() 的数字转换是严格的。如果一个值不完全是一个数字,就会失败:


alert( +"100px" ); // NaN


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

但在现实生活中,我们经常会有带有单位的值,例如 CSS 中的 "100px""12pt"。并且,在很多国家,货币符号是紧随金额之后的,所以我们有 "19€",并希望从中提取出一个数值。


这就是 parseIntparseFloat 的作用。

它们可以从字符串中“读取”数字,直到无法读取为止。如果发生 error,则返回收集到的数字。函数 parseInt 返回一个整数,而 parseFloat 返回一个浮点数:


alert( parseInt('100px') ); // 100
alert( parseFloat('12.5em') ); // 12.5
alert( parseInt('12.3') ); // 12,只有整数部分被返回了
alert( parseFloat('12.3.4') ); // 12.3,在第二个点出停止了读取


某些情况下,parseInt/parseFloat 会返回 NaN。当没有数字可读时会发生这种情况:


alert( parseInt('a123') ); // NaN,第一个符号停止了读取


parseInt(str, radix) 的第二个参数

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


alert( parseInt('0xff', 16) ); // 255
alert( parseInt('ff', 16) ); // 255,没有 0x 仍然有效
alert( parseInt('2n9c', 36) ); // 123456


其他数学函数


JavaScript 有一个内建的 Math[11] 对象,它包含了一个小型的数学函数和常量库。

几个例子:


Math.random():返回一个从 0 到 1 的随机数(不包括 1)。


alert( Math.random() ); // 0.1234567894322
alert( Math.random() ); // 0.5435252343232
alert( Math.random() ); // ... (任何随机数)


Math.max(a, b, c...)Math.min(a, b, c...):从任意数量的参数中返回最大值和最小值。


alert( Math.max(3, 5, -10, 0, 1) ); // 5
alert( Math.min(1, 2) ); // 1


Math.pow(n, power):返回 n 的给定(power)次幂。


alert( Math.pow(2, 10) ); // 2 的 10 次幂 = 1024


Math 对象中还有更多函数和常量,包括三角函数,你可以在 Math 对象文档[12] 中找到这些内容。


总结


要写有很多零的数字:

  • "e" 和 0 的数量附加到数字后。就像:123e6123 后面接 6 个 0 相同。
  • "e" 后面的负数将使数字除以 1 后面接着给定数量的零的数字。例如 123e-6 表示 0.000123123 的百万分之一)。


对于不同的数字系统:

  • 可以直接在十六进制(0x),八进制(0o)和二进制(0b)系统中写入数字。
  • parseInt(str,base) 将字符串 str 解析为在给定的 base 数字系统中的整数,2 ≤ base ≤ 36
  • num.toString(base) 将数字转换为在给定的 base 数字系统中的字符串。


对于常规数字检测:

  • isNaN(value) 将其参数转换为数字,然后检测它是否为 NaN
  • isFinite(value) 将其参数转换为数字,如果它是常规数字,则返回 true,而不是 NaN/Infinity/-Infinity


要将 12pt100px 之类的值转换为数字:

  • 使用 parseInt/parseFloat 进行“软”转换,它从字符串中读取数字,然后返回在发生 error 前可以读取到的值。


小数:

  • 使用 Math.floorMath.ceilMath.truncMath.roundnum.toFixed(precision) 进行舍入。
  • 请确保记住使用小数时会损失精度。


更多数学函数:

  • 需要时请查看 Math[13] 对象。这个库很小,但是可以满足基本的需求。


习题


先自己做题目再看答案。


1. 来自访问者的数字的总和


重要程度:🌟🌟🌟🌟🌟

创建一个脚本,提示访问者输入两个数字,然后显示它们的总和。

P.S. 有一个类型陷阱。


2. 为什么 6.35.toFixed(1) == 6.3?


重要程度:🌟🌟🌟🌟

根据文档,Math.roundtoFixed 都将数字舍入到最接近的数字:0..4 会被舍去,而 5..9 会进一位。


例如:


alert( 1.35.toFixed(1) ); // 1.4


在下面这个类似的示例中,为什么 6.35 被舍入为 6.3 而不是 6.4


alert( 6.35.toFixed(1) ); // 6.3


如何以正确的方式来对 6.35 进行舍入?


3. 重复,直到输入的是一个数字


重要程度:🌟🌟🌟🌟🌟


创建一个函数 readNumber,它提示输入一个数字,直到访问者输入一个有效的数字为止。


结果值必须以数字形式返回。


访问者也可以通过输入空行或点击“取消”来停止该过程。在这种情况下,函数应该返回 null


4. 一个偶发的无限循环


重要程度:🌟🌟🌟🌟

这是一个无限循环。它永远不会结束。为什么?


let i = 0;
while (i != 10) {
  i += 0.2;
}


5. 从 min 到 max 的随机数


重要程度:🌟🌟

内建函数 Math.random() 会创建一个在 01 之间(不包括 1)的随机数。

编写一个 random(min, max) 函数,用以生成一个在 minmax 之间的随机浮点数(不包括 max))。


运行示例:


alert( random(1, 5) ); // 1.2345623452
alert( random(1, 5) ); // 3.7894332423
alert( random(1, 5) ); // 4.3435234525


6. 从 min 到 max 的随机整数


重要程度:🌟🌟

创建一个函数 randomInteger(min, max),该函数会生成一个范围在 minmax 中的随机整数,包括 minmax


min..max 范围中的所有数字的出现概率必须相同。

运行示例:


alert( randomInteger(1, 5) ); // 1
alert( randomInteger(1, 5) ); // 3
alert( randomInteger(1, 5) ); // 5


你可以使用上一题的解决方案作为基础。


答案


请见:数字类型章节习题和答案


目录
相关文章
|
17天前
|
JavaScript 前端开发 开发者
如何在 JavaScript 中处理不同类型的错误?
【10月更文挑战第29天】通过对不同类型错误的准确识别和恰当处理,可以提高JavaScript程序的可靠性和稳定性,减少错误对程序运行的影响。
|
1月前
|
JavaScript 前端开发 安全
深入理解TypeScript:增强JavaScript的类型安全性
【10月更文挑战第8天】深入理解TypeScript:增强JavaScript的类型安全性
48 0
|
16天前
|
JavaScript 前端开发 Java
除了 JavaScript,还有哪些编程语言支持 Set 类型
【10月更文挑战第30天】这些编程语言中的 `Set` 类型虽然在语法和具体实现细节上有所不同,但都提供了类似的集合操作功能,方便开发者在不同的编程场景中处理集合相关的数据和逻辑。
|
17天前
|
存储 JavaScript 前端开发
js的基础类型和引用类型
【10月更文挑战第29天】理解 JavaScript 中的基础类型和引用类型的区别对于正确地编写代码和理解程序的行为非常重要。在实际开发中,需要根据具体的需求合理地选择和使用不同的数据类型,以避免出现一些意想不到的错误和问题。同时,在处理引用类型数据时,要特别注意对象的引用关系,避免因共享引用而导致的数据不一致等问题。
|
1月前
|
存储 JavaScript 前端开发
JavaScript 数据类型详解:基本类型与引用类型的区别及其检测方法
JavaScript 数据类型分为基本数据类型和引用数据类型。基本数据类型(如 string、number 等)具有不可变性,按值访问,存储在栈内存中。引用数据类型(如 Object、Array 等)存储在堆内存中,按引用访问,值是可变的。本文深入探讨了这两种数据类型的特性、存储方式、以及检测数据类型的两种常用方法——typeof 和 instanceof,帮助开发者更好地理解 JavaScript 内存模型和类型检测机制。
71 0
JavaScript 数据类型详解:基本类型与引用类型的区别及其检测方法
|
1月前
|
存储 Java Apache
Python Number类型详解!
本文详细介绍了 Python 中的数字类型,包括整数(int)、浮点数(float)和复数(complex),并通过示例展示了各种算术操作及其类型转换方法。Python 的 `int` 类型支持任意大小的整数,`float` 类型用于表示实数,而 `complex` 类型用于表示复数。此外,文章还对比了 Python 和 Java 在数字类型处理上的区别,如整数类型、浮点数类型、复数类型及高精度类型,并介绍了各自类型转换的方法。尽管两种语言在语法上有所差异,但其底层逻辑是相通的。通过本文,读者可以更好地理解 Python 的数字类型及其应用场景。
48 2
|
1月前
|
JavaScript 前端开发 开发者
【干货拿走】JavaScript中最全的数据类型判断方法!!!!
【干货拿走】JavaScript中最全的数据类型判断方法!!!!
23 1
|
1月前
|
存储 JavaScript 前端开发
JavaScript数据类型全解:编写通用函数,精准判断各种数据类型
JavaScript数据类型全解:编写通用函数,精准判断各种数据类型
19 0
|
2月前
|
存储 前端开发 JavaScript
前端基础(三)_JavaScript数据类型(基本数据类型、复杂数据类型)
本文详细介绍了JavaScript中的数据类型,包括基本数据类型(Number、String、Boolean、Undefined、Null)和复杂数据类型(Object),并解释了如何使用`typeof`操作符来识别变量的数据类型。同时,还讨论了对象、函数和数组等复杂数据类型的使用方式。
47 2
|
1月前
|
JavaScript 前端开发
JavaScript返回判断类型有哪些?
JavaScript返回判断类型有哪些?
31 0

热门文章

最新文章

下一篇
无影云桌面