在前台界面中,前端通常都要对数字进行处理,例如:取整;取两位小数;字符串转数字等等
NaN 非数字
在 JavaScript 中,NaN 代表 "Not-a-Number"(不是一个数字),它是一个特殊的值,用于表示一个未定义或不可表示的数值结果,注意它并非是个类型。 NaN给用户的体验是不好的,我们需要避免出现这样子的结果,通常在以下情况下会产生 NaN:
- 数学运算错误:当进行非法的数学运算时,例如 0 除以 0
let result = 0 / 0;
- 无效的数值转换:当尝试将无法转换为数值的字符串转换为数值时
let result = Number("abc");
- 操作数为 NaN:任何涉及 NaN 的运算结果都是 NaN
let result = NaN + 5;
怎么判断一个值是不是 NaN呢?
由于 NaN 不等于任何值,包括它自身,不能使用 == 或 === 来检测 NaN。可以使用 isNaN() 函数或 Number.isNaN() 方法来检测。
NaN === NaN // false
NaN == NaN // false
isNaN() 函数会先尝试将参数转换为数值,然后检测是否为 NaN。
console.log(isNaN(NaN)); // true
console.log(isNaN("abc")); // true
console.log(isNaN(123)); // false
强制类型转换,这意味着它可能会返回一些意外的结果,例如:
isNaN(NaN); // true
isNaN(undefined); // true
isNaN({}); // true
Number.isNaN() 方法不会进行类型转换,只在参数确实是 NaN 时返回 true。
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN("abc")); // false
console.log(Number.isNaN(123)); // false
所以,如果需要判断NaN时,建议使用 Number.isNaN
JavaScript 的 Number 对象
前面讲完了非数字,现在开始讲数字,在 JavaScript 中,只有一种数字类型,可以使用也可以不使用小数点来书写数字。例如
let num1 = 100;
let num2 = 3.14;
let billNum = 3e5; // 科学计数法
由于科学计数法的允许,导致 input 标签 type="number" 时,仍可输入 +,-,e 符号;
精度问题
在 JavaScript 中,实际上是不区分 整数和浮点数的,所有的数字都是 采用 IEEE754 标准定义的 64 位(双精度)浮点格式。其中 0 到 51 存储数字(片段),52 到 62 存储指数,63 位存储符号。由于存储的位数是有限的,而有些浮点数字转成二进制的时候位数是无限的,所以必然会存在截断,这就造成了JavaScript中的经典问题 0.1 + 0.2 = 0.30000000000000004;
(0.1).toString(2) // '0.0001100110011001100110011001100110011001100110011001101'
二进制转化步骤
-
0.1 转成二级制 0.1 × 2 = 0.2,整数部分是 0,小数部分是 0.2。 0.2 × 2 = 0.4,整数部分是 0,小数部分是 0.4。 0.4 × 2 = 0.8,整数部分是 0,小数部分是 0.8。 0.8 × 2 = 1.6,整数部分是 1,小数部分是 0.6。 0.6 × 2 = 1.2,整数部分是 1,小数部分是 0.2。 0.2 × 2 = 0.4,整数部分是 0,小数部分是 0.4。 0.4 × 2 = 0.8,整数部分是 0,小数部分是 0.8。 0.8 × 2 = 1.6,整数部分是 1,小数部分是 0.6。 0.6 × 2 = 1.2,整数部分是 1,小数部分是 0.2。 可以看到,小数部分开始重复了,这意味着 0.1 的二进制表示是一个无限循环小数。 结果:000110011……
-
13 转成二级制 13 ÷ 2 = 6,余数是 1。 6 ÷ 2 = 3,余数是 0。 3 ÷ 2 = 1,余数是 1。 1 ÷ 2 = 0,余数是 1。 结果:1011
由上面的转化过程可以看出,精度问题只存在小数的计算,整数的计算不会出现这种问题,在不得已必须前台做计算时,可以通过将小数转换为整数进行运算,然后再转换回小数。
let result = 0.1 + 0.2;
console.log(Math.round(result * 100) / 100); // 输出 0.3
区间问题
上面提到存储的位数是有限的,那么JavaScript中能表示的数值最大值和最小值肯定也是有上限的。
console.log(Number.MAX_VALUE) // 1.7976931348623157e+308
console.log(Number.MIN_VALUE) // 5e-324
进制问题
如果前缀为 0,则 JavaScript 会把数值常量解释为八进制数,如果前缀为 0 和 "x",则解释为十六进制数
var y = 0377;
var z = 0xFF;
默认情况下,JavaScript 数字为十进制显示。
但是你可以使用 toString() 方法 输出16进制、8进制、2进制
var myNumber=128;
myNumber.toString(16); // 返回 80
myNumber.toString(8); // 返回 200
myNumber.toString(2); // 返回 10000000
这里需要注意的一个问题是,不要使用 1.toString(2) 因为这里的. 会被当做小数点,所以应该写成 (1).toString(2) 或者 1..toString(2)
new Number
数字可以是数字也可以是对象;数字可以私有数据进行初始化,就像 x = 123;
JavaScript 数字对象初始化数据, var y = new Number(123);
var x = 123;
var y = new Number(123);
typeof(x) // 返回 Number
typeof(y) // 返回 Object
x === y // false
数字的操作
对于数字常见的算术运算操作是 加、减、乘、除、取余、指数
let a = 10;
let b = 3;
console.log(a % b); // 输出: 1
console.log(a ** b); // 输出: 1000
数学函数方法:
绝对值:Math.abs()
向上取整:Math.ceil()
向下取整:Math.floor()
四舍五入:Math.round()
取整:Math.trunc()
最大值:Math.max()
最小值:Math.min()
平方根:Math.sqrt()
幂运算:Math.pow()
随机数:Math.random()
常见的方法:
| 方法 | 描述 |
|---|---|
Number.parseFloat() | 将字符串转换成浮点数,和全局方法 parseFloat() 作用一致。 |
Number.parseInt() | 将字符串转换成整型数字,和全局方法 parseInt() 作用一致。 |
Number.isFinite() | 判断传递的参数是否为有限数字。 |
Number.isInteger() | 判断传递的参数是否为整数。 |
Number.isNaN() | 判断传递的参数是否为 NaN。 |
Number.isSafeInteger() | 判断传递的参数是否为安全整数。 |
将字符串转成数字,我们也可以使用 Number 构造函数,那么 它和 parseFloat的区别呢?
let num3 = parseFloat('123abc'); // 123
let num4 = parseFloat('abc123'); // NaN
console.log(Number('123abc')); // 输出: NaN
console.log(Number('abc123')); // 输出: NaN
Number():更严格,只要字符串中有任何非数字字符(除了前导和尾随空格),就会返回 NaN
parseFloat():更宽松,只要字符串以数字开头,就会解析该数字,忽略后续的非数字字符。
vue的 指令 v-model修饰符 .number
在vue的内置指令 v-model 含有修饰符 .number 和 .lazy 等,但是这个 .number 好像没那么的好用,我们看看源码一探究竟
/**
* "123-foo" will be parsed to 123
* This is used for the .number modifier in v-model
*/
export const looseToNumber = (val: any): any => {
const n = parseFloat(val)
return isNaN(n) ? val : n
}
由于vue中使用的是 parseFloat 是非严格模式,所以 输入框中 是可以 输入 ``123abc这种字符串的,只是绑定的值会被转成123` 而已
数字格式化
指定小数位
toFixed(),数字类型将转成字符串,使用时记得做非空校验,也可以 num?.toFixed(2)
let num = 1234.56789;
console.log(num.toFixed(2)); // 输出: "1234.57"
金额格式化
金额格式化,我们一般会采用封装好的工具函数库,当然我们也可以使用原生api自己封装,使用到的api:Intl.NumberFormat
let num = 1234567.89;
// 默认格式
let formatter = new Intl.NumberFormat();
console.log(formatter.format(num)); // 输出: "1,234,567.89"
// 指定语言和选项
formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
console.log(formatter.format(num)); // 输出: "\$1,234,567.89"
formatter = new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR'
});
console.log(formatter.format(num)); // 输出: "1.234.567,89 €"
formatter = new Intl.NumberFormat('en-IN', {
maximumSignificantDigits: 3
});
console.log(formatter.format(num)); // 输出: "1,230,000"
其他复杂格式
对于复杂的格式化,我们可以采用正则的方式替换,或者自行封装,下面是几个举例:
function formatNumberWithCommas(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
let num = 1234567.89;
console.log(formatNumberWithCommas(num)); // 输出: "1,234,567.89"
function formatAsPercentage(num, decimals = 2) {
return (num * 100).toFixed(decimals) + '%';
}
let num = 0.1234;
console.log(formatAsPercentage(num)); // 输出: "12.34%"