JavaScript 数字与基本运算
前面认识了类型,这一篇深入第一种类型,number 数字。数字是程序世界里最常用的数据,计算是编程最基本的能力。这篇把数字类型和算术运算讲透,顺带处理两个著名的大坑。
整数与浮点数
JavaScript 只有 number 一种数字类型,整数和小数统一处理,没有 int、float 之类的区分。5 和 5.0 是同一个值。
console.log(5 === 5.0); // 输出 true,同一个值
这跟 C、Java 等语言差别很大,那里 5 是整数、5.5 是浮点数,类型严格区分。JavaScript 一个类型全包,写起来省心,代价是精度上有限制,后面会讲到。
算术运算符
数字运算用算术运算符,包括 + - * / % ** 六种,分别是加、减、乘、除、取余、幂。
console.log(7 + 3); // 输出 10
console.log(7 - 3); // 输出 4
console.log(7 * 3); // 输出 21
console.log(7 / 3); // 输出 2.3333333333333335
console.log(7 % 3); // 输出 1,7 除以 3 余 1
console.log(2 ** 3); // 输出 8,2 的 3 次方
% 是取余数运算符,7 除以 3 商 2 余 1,结果为 1。它的常见用途包括判断奇偶,数字 % 2 等于 0 是偶数,等于 1 是奇数。** 是幂运算符,2 ** 3 表示 2 的三次方,等于 8。
特殊值 NaN 与 Infinity
有些运算结果不是正常数字,JavaScript 用两个特殊值表示。NaN 全称 Not a Number,意思是”不是一个有效数字”。Infinity 表示无穷大。
console.log(0 / 0); // 输出 NaN
console.log(1 / 0); // 输出 Infinity
0 除以 0 没有数学意义,结果是 NaN。1 除以 0 的结果是正无穷,JavaScript 用 Infinity 表示。负数的同类情况输出 -Infinity。
NaN 的特性,判断要用 Number.isNaN()
NaN 最反直觉的特性是,它不等于任何值,包括它自己。
console.log(NaN === NaN); // 输出 false,NaN 连自己都不等于
所以不能用 === 判断一个值是不是 NaN。判断 NaN 的正确方式是 Number.isNaN() 方法,返回布尔值。
console.log(Number.isNaN(0 / 0)); // 输出 true
console.log(Number.isNaN(42)); // 输出 false
console.log(Number.isNaN('abc')); // 输出 false,字符串不是 NaN
NaN 有传染性,任何和 NaN 参与的计算结果都是 NaN,所以程序里出现 NaN 要尽快定位来源,通常是某个值参与了本不该参与的运算。
浮点数精度问题
这是 JavaScript 数字最著名的坑,0.1 + 0.2 不等于 0.3。
console.log(0.1 + 0.2); // 输出 0.30000000000000004
原因很简单,计算机用二进制存数字,而 0.1 这类小数无法用二进制精确表示,就像三分之一无法用十进制小数精确表示,存储时产生了舍入误差,运算后误差就露出来了。
这个问题不止 JavaScript 有,任何用二进制浮点数的语言都有,只是 JS 的 number 只有一种类型,藏不住。
实用处理办法有两个。第一,比较或显示时用 toFixed() 保留位数,它返回字符串
console.log((0.1 + 0.2).toFixed(1)); // 输出 "0.3"
第二,涉及金额等精确场景时,先放大成整数运算,再缩小回来。比如把元换算成分,整数运算没有精度问题
const price = 1990; // 19.9 元换算成分
const total = price * 3; // 5960 分,即 59.6 元
日常开发中普通计算直接用就行,误差只在极少数场景需要处理,比如金额、坐标。知道有这个坑和对应的处理办法,遇到时就不会手足无措。
小结
- JavaScript 只有 number 一种数字类型,没有 int、float 之分,5 和 5.0 是同一个值。
- 算术运算符包括 + - * / % ,% 取余数, 幂运算。
- NaN 表示不是有效数字,是 0 / 0 的结果。Infinity 表示无穷大,是 1 / 0 的结果。
- NaN 不等于任何值包括自己,判断用 Number.isNaN()。
- 0.1 + 0.2 不等于 0.3 是二进制精度问题,可以用 toFixed() 保留位数,或先放大成整数再运算。