Skip to content
This page has been auto-translated and may contain errors.View in English

数字和数学运算

docs.scrimba.com

购物车会累加价格,游戏会记录分数,表单会计算要收取的税款。这一切都归结为数字和对它们进行算术运算。JavaScript 将数字作为其核心构建块之一,本章介绍的是可以对它们进行的操作:日常数学、用于舍入和随机性的内置辅助函数,以及困扰几乎所有人的部分——将在页面中输入的文本转换回可以相加的数字。

数字和算术运算

JavaScript 有一个**number** 类型,它覆盖了所有内容。整数如 42 和小数如 9.99 都是同一种类型的值,所以你永远不需要在它们之间选择:

js
const price = 9.99;
const quantity = 3;
const total = price * quantity;
console.log(total); // 29.97

你可以用已知的符号进行数学运算:+ 加法,- 减法,* 乘法,/ 除法。还有一个不太熟悉的 %,是余数运算符。它给出除法后的余数:

js
const remainder = 7 % 2;
console.log(remainder); // 1

7 除以 2 等于 3 余 1,所以 7 % 21。这是检查数字是否为偶数的方法:偶数除以 2 没有余数,所以偶数的 number % 20,奇数是 1

JavaScript 有一个单一的**number** 类型,用于整数和小数,所以 429.99 是同一种类型的值。这不同于将整数和浮点数分为不同类型的语言;这里你永远不需要声明你的意思是哪一个。

五个算术运算符是 +-*/%。余数运算符 % 返回除法后的余数,其用处比初学者预期的要大:

js
const total = 17;
const perRow = 5;
const leftOver = total % perRow;
console.log(leftOver); // 2

将 17 个项目按每行 5 个排列会在最后一行留下 2 个,17 % 5 会告诉你这一点。经典用法是测试奇偶性:count % 2 对偶数是 0,对奇数是 1,这就是如何交替行颜色或将列表分成两部分的方法。

运算符遵循正常的数学优先级,所以 */% 的优先级高于 +-,相同优先级的运算符从左到右读取。当顺序重要时,使用括号:

js
const withTax = (100 + 20) * 1.1;
console.log(withTax); // 132

JavaScript 有一个**number** 类型,是一个 64 位浮点值(IEEE 754 双精度,与大多数语言称为 double 的表示相同)。这个级别没有单独的整数类型,这意味着 429.99-3 都是同一种东西,你写的每个整数实际上都是一个恰好没有小数部分的浮点数。

五个运算符是 + - * / %。余数 % 值得深入了解,因为它是余数,不是模数:它取左操作数的符号,所以 -7 % 3-2,而不是 1。在日常情况下其值是稳定且有用的。测试 n % 2 === 0 来判断偶数是经典技巧,对负数也适用,因为 -4 % 2 仍然是 0

js
const total = 128;
const pageSize = 25;
const onLastPage = total % pageSize;
console.log(onLastPage); // 3

优先级遵循标准数学:*/% 共享一个优先级,高于 +-,同一优先级内从左到右计算(左结合)。整个语法在涉及比较和逻辑时会有更多层次,这是运算符的主题。仅对算术而言,你需要的规则很简单:乘法在加法之前,对任何读者需要暂停思考的内容使用括号。清晰胜过节省三个字符。

js
const average = (a + b + c) / 3;
Juno数字和算术运算 JavaScript 中有一个 number 类型,所以整数和小数是同一种东西,你永远不需要在它们之间选择。运算符是 + - * / 加上 %(余数),它是除法后剩下的。巧妙的技巧是 % 2:对偶数是 0,对奇数是 1
Juno数字和算术运算 一个 number 类型覆盖整数和小数,所以没有 int 对比 float 的决定要做。五个运算符是 + - * /%% 值得记住:count % 2 给出奇偶性,它也告诉你部分填充的行中剩下多少。优先级是正常的数学,所以对任何读者需要思考的内容使用括号。
Juno数字和算术运算 每个数字都是 64 位浮点数,所以没有单独的整数类型,42 是一个没有分数的双精度数。% 中的一个陷阱是它是余数而不是模数,所以它取左侧的符号,-7 % 3-2。对于奇偶测试,这永远不会咬你,因为 n % 2 === 0 对负数也成立。

Math 对象

当你需要超过基本运算符的功能时,JavaScript 给你**Math**,一个内置的数学辅助函数集合。你用点号按名称调用它们:

js
console.log(Math.round(4.6)); // 5
console.log(Math.floor(4.6)); // 4
console.log(Math.ceil(4.1)); // 5

Math.round 四舍五入到最近的整数,Math.floor 总是向下舍入,Math.ceil 总是向上舍入。还有 Math.maxMath.min,它们选择你给出的数字中最大或最小的:

js
console.log(Math.max(3, 8, 5)); // 8
console.log(Math.min(3, 8, 5)); // 3

还有 Math.random,它给出一个 0 到 1(不包括 1)之间的随机小数。单独它很少是你想要的,所以常见的做法是将其乘以一个数并向下取整来得到一个随机整数:

js
const roll = Math.floor(Math.random() * 6) + 1;
console.log(roll); // 4(每次都是不同的数字)

Math.random() * 6 产生 0 到 6 之间的某个数,Math.floor 将其降低到 0 到 5 之间的整数,+ 1 将其转移到 1 到 6 的骰子掷出。

Math 是一个内置对象,包含数学函数和常量。你不创建它;它总是存在的,你用点号调用它的方法。

舍入三部曲是你最常使用的:

js
console.log(Math.round(4.5)); // 5,最近的整数
console.log(Math.floor(4.9)); // 4,总是向下
console.log(Math.ceil(4.1)); // 5,总是向上

Math.maxMath.min 返回其参数中最大和最小的,这对于将值夹在范围内很有用:

js
const clamped = Math.min(Math.max(userValue, 0), 100);
console.log(clamped); // 保持在 0 到 100 之间

Math.random 返回范围 [0, 1) 中的浮点数,意思是 0 是可能的但 1 永远不是。要将其变成从 0 到 n 的随机整数,模式是 Math.floor(Math.random() * n)

js
const index = Math.floor(Math.random() * 5);
console.log(index); // 0 到 4 之间的整数

乘法将范围扩展到 [0, n),向下取整会切断小数,所以你能得到整数。当你想要不从零开始的范围时添加偏移量,比如掷骰子时的 + 1

Math 是一个静态命名空间对象:一个普通的数学方法和常量容器,不是你实例化的东西。每个方法都被称为 Math.something(),并且运行时中恰好有一个 Math

舍入方法各自向不同的目标舍入:Math.round 向最近舍入(平局向上,所以 Math.round(2.5)3Math.round(-2.5)-2),Math.floor 向负无穷,Math.ceil 向正无穷。floor 不是截断的区别对负数重要,其中 Math.floor(-1.2)-2,不是 -1

js
console.log(Math.floor(-1.2)); // -2,floor 即使对负数也向下

Math.maxMath.min 接受任何数量的参数,是夹值的清晰方式:

js
const clamped = Math.min(Math.max(value, min), max);

Math.random 返回范围 [0, 1) 中的均匀分布的浮点数。随机整数的习语是 Math.floor(Math.random() * range) + start:按范围宽度缩放,向下取整得到整数,按起始值移位。两个值得记住的注意事项:它在加密上不安全,所以永远不要将其用于令牌或用户不应该能够猜到的任何东西(crypto.getRandomValues 存在于此目的),以及差一错误来自混淆包含和排除边界,所以提前决定你的最高值是否可达。

js
const dice = Math.floor(Math.random() * 6) + 1;
console.log(dice); // 1 到 6(包括两者)
JunoMath 对象Math 是一个现成的数学辅助函数工具箱,你用点号调用:Math.roundMath.floor(向下)、Math.ceil(向上),以及 Math.max / Math.min 用于最大和最小值。Math.random 给出 0 到 1 之间的小数,值得记住的公式是 Math.floor(Math.random() * n) 来得到随机整数。我花了好几次尝试来相信这个模式,所以复制它并慢慢读它。
JunoMath 对象Math 总是存在的,无需设置,你用点号调用其方法。记住舍入三部曲:round 向最近舍入,floor 向下,ceil 向上,并一起使用 minmax 将值夹在范围内。对于随机性,Math.floor(Math.random() * n) 给出 0 到 n 之间的整数,偏移量移动范围。
JunoMath 对象Math 是一个静态命名空间,所以有一个它并且无需构造。观察负数舍入目标:Math.floor(-1.2)-2,不是截断,Math.round 打破平局向上。Math.floor(Math.random() * range) + start 是整数习语,但 Math.random 不安全,所以对用户不应该猜到的任何东西使用 crypto.getRandomValues

将字符串转换为数字

任何人在页面上输入的东西都以**文本**形式到达,即使看起来像数字。在框中输入 5,你得到字符串 "5",而不是数字 5。这很重要,因为两个字符串上的 + 会连接它们而不是相加:

js
const a = "5";
const b = "3";
console.log(a + b); // "53",不是 8

要进行数学运算,你首先将文本转换为数字。最清晰的方法是 Number()

js
const count = Number("5");
console.log(count + 3); // 8

还有两个你会看到的:parseInt 读取整数并丢弃任何小数,parseFloat 保留小数:

js
console.log(parseInt("42px")); // 42
console.log(parseFloat("9.99")); // 9.99

这两者都可以读取坐在某些文本开头的数字,比如 "42px",这是 Number 做不到的。还有一个快捷方式,在字符串前放一个 +,它的转换方式与 Number 相同:

js
console.log(+"7"); // 7

来自页面的输入总是**字符串**。表单字段、URL、数据属性:全是文本,即使是 "5"。由于 + 连接字符串,添加两个文本值会连接它们而不是求和,这是初学者最常见的数字错误:

js
const price = "10";
const shipping = "5";
console.log(price + shipping); // "105",字符串连接

在计算之前转换。Number() 是严格的、易读的选择:它将干净的数字字符串变成数字,对任何不完全是数字的东西给出 NaN

js
console.log(Number("42")); // 42
console.log(Number("42px")); // NaN,不完全是数字

parseIntparseFloat 更宽容。它们从字符串的前面读取一个数字,并在第一个不适合的字符处停止,这就是为什么 parseInt("42px")42parseInt 丢弃任何小数部分;parseFloat 保留它:

js
console.log(parseInt("42px", 10)); // 42
console.log(parseFloat("3.14 radians")); // 3.14

传递 10 作为 parseInt 的第二个参数,这样它总是读取十进制。一元 + 是一个简洁的转换器,行为像 Number对于完整值使用 Number(),对于嵌入在文本中的数字使用 parseInt/parseFloat

来自外部世界的文本总是**字符串**,因为 + 对连接重载,"10" + "5""105"。除了 + 之外的每个算术运算符都会将其操作数强制转换为数字,所以 "10" * 110,但依赖这一点是可读性陷阱。故意转换。

Number() 是全字符串强制转换:只有在整个修剪后的字符串是有效的数字字面量时才成功,否则 NaN。它理解十六进制(Number("0x1f")31)、指数和前导或尾随空格,它将 "" 视为 0,这是一个值得知道的尖锐边缘。

js
console.log(Number("")); // 0,一个值得错过的尖锐边缘
console.log(Number("  12 ")); // 12,空格被修剪
console.log(Number("12abc")); // NaN

parseIntparseFloat 解析一个前缀:它们尽可能地读取并忽略其余部分,所以 parseInt("12abc")12。始终给 parseInt 其基数(parseInt(str, 10));没有它,像 "0x10" 这样的输入被读作十六进制。一元 + 是最短形式的 Number 强制转换,在简洁代码中经常出现(+"3.5"3.5),具有相同的规则和相同的 "" 变成 0 的惊喜。

实际规则:当整个值应该是数字且其他任何东西都是你想捕获的错误时使用 Number(或 +),当数字嵌入在你期望的文本中时使用 parseInt/parseFloat。无论你选择哪个,结果都可以是 NaN,这是下一节的内容。超越此的比较和强制转换存在于运算符,故事的字符串方面在字符串

Juno将字符串转换为数字 任何在页面上输入的内容都是文本,所以 "5" 不是数字 5"5" + "3" 给出 "53" 而不是 8。首先转换:Number("5") 最清晰,parseInt 读取整数,parseFloat 保留小数,字符串前的 + 是快速快捷方式。注意连接而不是相加的陷阱,它会困住每个人一次。
Juno将字符串转换为数字 页面输入总是字符串,由于 + 连接字符串,"10" + "5""105",最常见的数字错误。当整个值应该是数字时使用 Number(),当数字坐在文本中时使用 parseIntparseFloat,比如 "42px"。给 parseInt 其十进制参数,它不会让你惊讶。
Juno将字符串转换为数字+ 外的每个运算符都强制转换为数字,所以 "10" * 1 有效,但故意转换。Number() 要求整个字符串是数字并悄悄地将 "" 变成 0,而 parseIntparseFloat 解析一个前缀并忽略其余部分,所以始终给 parseInt 一个 10 的基数。它们中任何一个都可以给你 NaN,这是下一个要习惯的东西。

数字的尖锐边缘

JavaScript 中的数字包含一个值得早期看一次的惊喜。添加两个简单的小数,你可以得到一个奇怪的答案:

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

这不是你代码中的错误。计算机以无法准确表示每个值的方式存储小数,所以会产生微小的误差。它很少重要,当它重要时,你舍入结果以供显示:

js
const sum = 0.1 + 0.2;
console.log(Math.round(sum * 100) / 100); // 0.3

你还会遇到一个称为 NaN 的值,简称"非数字"。当数学运算没有合理的答案时,你会得到它,比如尝试将无意义的文本转换为数字:

js
console.log(Number("hello")); // NaN

在你的输出中看到 NaN 通常意味着某处的转换出错了,所以它是一个有用的线索,而不是要害怕的东西。

两个数字值最终会让你惊讶,最好是有意识地认识它们。

第一个是**浮点**不精确性。因为小数以二进制存储,某些值无法准确保存,所以 0.1 + 0.2 不完全是 0.3

js
console.log(0.1 + 0.2 === 0.3); // false

实际的修复是为显示舍入,或在整个单位中工作。金钱是经典案例:将金额保留为整数分(1099 而不是 10.99)并仅在显示时除以,这完全避免了错误。

js
const cents = 1099;
console.log(cents / 100); // 10.99,精确显示

第二个是 NaN,"非数字",操作的结果没有数字答案,比如 Number("hello")0 / 0。它最奇怪的属性是它不等于自己:

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

所以你不能用 === 测试它。使用 Number.isNaN(value),它只对 NaN 返回 true

js
console.log(Number.isNaN(Number("hello"))); // true

还有 Infinity,你从除以零(1 / 0)或超出数字范围得到的。它在比较中表现得像真实值但表示计算已超出边缘。

三个值坐在数字类型的边缘,一个专业人士需要本能地记住这三个。

**浮点**不精确性是著名的。IEEE 754 以二进制存储数字,像 0.1 这样的值没有精确的二进制表示,所以算术会累积舍入错误:

js
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false

当任何一边来自算术时永远不要用 === 比较浮点数。两个在生产中成立的修复:为显示舍入到固定精度,或者对于任何要求精确性的地方(如货币),在最小整数单位中工作,仅在边界转换。将金钱存储为整数分(1099)并在输出时格式化会删除一类错误而不是修补一个实例。

js
const totalCents = 1099 + 250;
console.log(totalCents); // 1349,精确的整数算术

NaN 是数字类型的错误值,由 0 / 0Number("x") 和任何具有未定义数字结果的操作产生。它有规范规定的独特属性:它是唯一不等于自己的值,所以 NaN === NaNfalse。这就是为什么身份检查对其失败以及为什么 Number.isNaN(x) 作为可靠的测试存在。相对于较旧的全局 isNaN,更喜欢 Number.isNaN 形式,它首先强制转换其参数,因此对不实际是 NaN"hello" 之类的东西报告 true

js
console.log(NaN === NaN); // false,NaN 永远不等于自己
console.log(Number.isNaN(0 / 0)); // true,可靠的测试

Infinity(和 -Infinity)是你从 1 / 0 或超出可表示范围得到的。它是一个真实的、可比较的值(Infinity > 1e308true),这使其成为"尚无限制"的方便哨兵,在寻找最小值时,但如果它到达显示或数据库时会感到惊讶。当结果可能溢出或除以零时,在 Infinity 泄露下游之前决定什么是合理的回退。

Juno数字的尖锐边缘 小数存储有点松散,所以 0.1 + 0.2 出来是 0.30000000000000004,这是正常的,不是你代码中的错误。当它重要时,在显示之前舍入结果。而 NaN 意味着"非数字",当转换失败时你得到的答案,所以把它当作某些文本没有按预期变成数字的线索。
Juno数字的尖锐边缘 浮点意味着 0.1 + 0.2 不完全是 0.3,所以为显示舍入或使用整个单位(如金钱的整数分)。NaN 是"无数字答案"值,其陷阱是 NaN === NaNfalse,所以改用 Number.isNaN 测试。Infinity 显示来自除以零和意味着计算超出边缘。
Juno数字的尖锐边缘 永远不要用 === 比较算术浮点数;舍入到精度或保持金钱在整数分,这样错误类会消失。NaN 是唯一不等于自己的值,这正是为什么 Number.isNaN 存在,并且相对于强制转换的全局 isNaN 使用它。Infinity 是来自除以零或溢出的真实可比较值,所以在它到达显示或数据库之前给它一个合理的回退。

数字接下来去哪里

数字是 JavaScript 中少数几个核心数据类型之一,与文本和真/假值并排。大多数真实程序混合使用它们,从字符串中提取数字、比较它、将结果存储在变量中,进行比较和组合的运算符是接下来运算符的主题。