js - 数值 & 数值的转化

474 阅读7分钟

承接上一篇文章 # typeof 和 undefined,Null,Boolean 类型,Number 内容过多,因此另起一篇文章

Number 类型

Number 类型使用 IEEE754 格式表示整数和浮点数(双精度值)。不同的数据类型相应地也有不同的数值字面量格式

十进制

直接写出来即可

const intNum = 2023;

其他进制

使用八进制和十六进制格式化创建的数值在所有数学操作中都被视为十进制数值

八进制

对于八进制字面量,第一个数字必须是零,然后是相应的八进制数字(0~7),如果字面量中包含的数字超出了应有的范围,就会忽略前缀的零,然后后面的数字序列会被当成十进制

const octalNum1 = 070; // 八进制 56;
const octalNum2 = 079; // 无效的八进制,当成 79处理
console.log(octalNum2); // 79

注意⚠️: 八进制字面量在严格模式下是无效的,会导致 js 引擎抛出语法错误,如下

// 开启严格模式
'use strict';
const octalNum1 = 070; // SyntaxError: Octal literals are not allowed in strict mode.

十六进制

十六进制字面量,必须要有 0x(区分大小写),然后是16 进制数字(0 ~ 9 以及 A ~ F,十六进制数字中的字母大小写均可)。

const hexNum1 = 0xa; // 十六进制10
const hexNum2 = 0x1f; // 十六进制31

console.log(hexNum1); // 10
console.log(hexNum2); // 31

浮点数

要定义浮点数,数值中必须包含小数点,而且小数点后面必须至少有一个数字,虽然小数点前面不是必须有整数,但推荐加上

注意⚠️: 浮点值的精确度最高可达17位小数,因此在转换的过程中会导致精度丢失的问题

const float1 = 1.1;
const float2 = 0.1;
// 有效,但是不推荐
const float3 = 0.1;

因为储存浮点值使用的内存空间是存储整数数值的两倍,所以 ECMAScript 总是想方设法把值转化为整数,小数点后没有数字或者全部为零的情况数值会被转化成整数。

const float1 = 1.0; // 小数点之后全是零,当作整数处理
const float2 = 1; // 小数点之后没有数值(或没有小数点),当整数处理

科学计数法

对于非常大或者非常小的数值,浮点值可以用科学记数表示,科学记数法用于表示一个应该乘以 10 的给定次幂的数值。格式是: 数值跟一个大写或小写的字母e再加上要乘的10的多少次幂

const num = 3.125e7; // 等于 3.126*10^7;这种表示方式实际上相当于说: 以 3.125 作为系数乘以10的7次幂;

精准度问题

使用 IEEE754 数值的编程语言都可能面对精度丢失的问题,因此永远不要测试某个特定的浮点值

console.log(0.1 + 0.2 === 0.3); // false, 这就是精度丢失

值的范围

由于内存的限制,数值的范围在: [Number.MIN_VALUE,NUMBER.MAX_VALUE] 中,超过了最大的值会被转化为 Infinity (无穷大),超过了最小的值会被转化为 -Infinity (负无穷大),可以使用isFinite(xxx)来判断一个值是否有限大

const result = Number.MAX_VALUE + Number.MAX_VALUE;
console.log(result); // Infinity
// 因为结果 已经为 Infinity 了, 不是有限的值了
console.log(isFinite(result)); // false

NaN (Not a Number)

NaN 表示本来要返回数值的操作失败了(而不是抛错误),比如 +0,-0,0 相除,字符串转换为数字失败

console.log(23 / 0); // Infinity
console.log(-0 / 0); // NaN
console.log(Number('not str num')); // NaN

注意⚠️: NaN 不等于 NaN 不论是否严格判断

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

isNaN(x)

isNaN(x), 判断 x 是否 “不是数值”,可作为数值的有

  • 字符串数字可以是 数值, "10" 可以转化为 10 数值
  • boolean 值 可以是数值, true 可以转化为数值1, false 可以转化为 0
const printIsNaN = (x) => console.log(isNaN(x));
// 如下打印的 true 表示 “不是一个 数值”,false 则表示 x 是一个数值
printIsNaN(NaN); // true
printIsNaN(10); // false
printIsNaN('10'); // flase
printIsNaN('str'); // true
printIsNaN(true); // false
printIsNaN('1122ddd'); // true
isNaN 测试对象执行的过程

注意:首先会调用对象的 valueOf() 方法,然后在确定返回的值是否可以转化为 数值,如果不能,再调用 toString() 方法,并测试其返回值。

数值转换

将非数值转化为数值:

  • Number(xx);
  • parseInt(xx);
  • parseFloat(xx);

Number(xx) 是转型函数,可用于任何数据类型。parseInt 和 parseFloat 主要用于将字符串转化为 数值。

Number(xx) 函数基于如下规则执行转换

一元加操作符与 Number()函数遵循相同的转化规则

  • 布尔值, true 转化为 1, false 转化为 0
  • 数值,直接返回
  • null, 返回 0
  • undefined, 返回 NaN
const printNumberValue = (x) => console.log(Number(x));
printNumberValue(false); // 0
printNumberValue(true); // 1
printNumberValue(33); // 33, Number 直接返回数值
printNumberValue(null); // 0
printNumberValue(undefined); // NaN
  • 字符串,规则:
    • 字符串包含数值字符(包括字符前边有 +、- 号的情况),则将其转化为十进制数值
    • 字符串包含有效的浮点值格式(如 “1.1”)则会转化为相应的浮点值(同样,忽略前边无效的零)
    • 字符串包含有效的十六进制格式(如 "0xf", 则会转化为与该十六进制值对应的十进制整数值
    • 如果是空字符串(不包含任意字符),返回 0
    • 如果字符串包含除上述情况外的其他字符,则返回 NaN
const printNumberValue = (x) => console.log(Number(x));
printNumberValue('-3'); // -3
printNumberValue('+3'); // 3
printNumberValue('3'); // 3
printNumberValue('011'); // 11 忽略前边的 0
printNumberValue('01.1'); // 1.1 忽略前边的 0
printNumberValue('0xf'); // 15, look,这里的十六进制字符串被转化为其对应的十进制值
printNumberValue(''); // 0,这里是空字符串
printNumberValue('     '); // 0,这里是空字符串
printNumberValue('   a  '); // NaN 包含了 a 字符
  • 对象,调用对象的 valueOf() 方法,并按照上述转化返回值,如果没有 valueOf() 方法,则调用 toString() 方法,再按照转化字符串的规则转化
const printNumberValue = (x) => console.log(Number(x));
const obj1 = {
  valueOf() {
    return 23;
  },
};

// valueOf 方法和 toString 方法都有
const obj2 = {
  valueOf() {
    return NaN;
  },
  toString() {
    return '33';
  },
};

// 没有 valueOf 方法
const obj3 = {
  toString() {
    return '33';
  },
};
printNumberValue(obj1); // 23, 返回 valueOf 方法的 23
printNumberValue(obj2); // NaN,valueOf 方法的转化结果是 NaN
printNumberValue(obj3); // 33,无 valueOf 方法,则调用 toString 得到的结果转化后为 33

小结: Number 函数的弊端: 太 TM 复杂了,因如果需要得到整数时可以优先使用 parseInt(xx) 函数

parseInt(xx) \ parseFloat(xx)

parseInt 和 parseFloat 更加专注于字符串是否包含数值模式; parseInt 的特点有:

  • 字符串最前面的空格会被忽
  • 从第一个非空字符串开始转化,如果第一个字符不是数值字符、加号或者减号,parseInt 会立即返回 NaN,这意味着空字符串也会返回 NaN
  • 如果第一个字符是数值字符、加号或减号,则继续依次检测每个字符,直到字符串末尾,或碰到非数值字符
  • 假设字符串中第一个字符是数值字符(十进制、八进制(0开头)、十六进制(0x开头))会将其转化为对应的十进制值
  • 接受第二个参数用于指定底数(进制数,8,16,10)
const printParseInt = (x, radix) => console.log(parseInt(x, radix));
printParseInt(''); // NaN
printParseInt('123xxx'); // 123, 只解析数值字符
printParseInt('0xA'); // 10, 解析为十六进制对应的十进制值
printParseInt(22.5); // 22
printParseInt('070'); // 70 解析为 70 (十进制)
// 指定进制
printParseInt('070', 8); // 56,将 070 作为 8 进制转换为 10进制的值
printParseInt('10', 2); // 2,将 10 作为 2 进制转换为 10进制的值
printParseInt('10', 8); //8,将 10 作为 8 进制转换为 10进制的值
printParseInt('10', 16); //16,将 10 作为 16 进制转换为 10进制的值

parseFloat 函数的工作方式跟 parseInt() 函数类似,都是从位置 0 开始检测每个字符,parseFloat 的特点有:

  • 解析到字符串末尾或者解析到一个无效的浮点数为止,意味着第一次出现的小数点是有效的,但是第二次出现的小数点就是无效了(此时字符串的剩余字符都会被忽略)
  • 它始终忽略字符串开头的零,parseFloat 只解析十进制值,因此不能指定底数(基数)
  • 如果字符串表示整数(没有小数点或者小数点后面只有一个零,则返回整数
const printParseFloat = (x) => console.log(parseFloat(x));

printParseFloat('1234xxx'); // 1234 按照整数解析
printParseFloat('0xA'); // 0, 只能解析 十进制的数值字符
printParseFloat('23.333'); // 23.333
printParseFloat('23.3.3'); // 23.3, 第二个小数点无效
printParseFloat('0903.3.3'); // 903.3, 前边的 0 会被省略,第二个小数点无效
printParseFloat('3.14e2'); // 314 科学计数法