跳到主要内容

类型转换/运算规则

运算规则​

  • 一个布尔值和一个对象进行比较时,会将这两个值转换为数字进行最后的比较
    • 注意:全等比较的时候不会进行类型转换

例题:

为什么“false == []”和“false == ![]”都返回true?

js
console.log(Number([])); // 空数组转换数字为 0
console.log(Number(false)); // false 转换为数字 0

// 一个布尔值和一个对象进行比较时,会将这两个值转换为数字进行最后的比较
console.log(false == []); // true

理解valueOf和toString​

  • valueOf和toString二者并存情况下

    • 数值运算优先使用valueOf
    • 字符串运算中,优先调用了toString

    valueOf偏向于运算,toString偏向于显示。

  • 在进行对象转换时,将优先调用toString方法,如若没有重写 toString,将调用 valueOf 方法;如果两个方法都没有重写,则按Object的toString输出。

  • 在进行强转字符串类型时,将优先调用 toString 方法,强转为数字时优先调用 valueOf。

  • 使用运算操作符的情况下,valueOf的优先级高于toString。

toString场景/作用​

  • toString: 返回该对象的字符串

1、字符串使用toString会原样输出

2、数组的使用toString调用,数组的内容以逗号连接字符串, 相当于Array.join(',')

3、对象使用toString,会返回 [object Object]

4、精准的判断类型

5、使用操作符的时候, 如果其中一边是对象,会优先调用toString方法【隐式转换】,然后才进行操作

javascript
// 字符串使用toString会原样输出
const str = '123';
console.log('str', str.toString()); // '123'

// 数组的使用toString调用,数组的内容以逗号连接字符串, 相当于Array.join(',')
const arr = [1, 2, 3];
console.log('arr', arr.toString()); // '1,2,3'
console.log('arr', arr.join() === arr.toString()); // true

// 对象使用toString,会返回 [object Object]
const obj = {};
console.log('obj', obj.toString()); // [object Object]

// 精准的判断类型
console.log(Object.prototype.toString.call(2)); // [object Number]

// 使用操作符的时候, 如果其中一边是对象,会优先调用toString方法【隐式转换】,然后才进行操作
const arr1 = [1, 2, 3];
// 对象使用toString方法,数组使用了toString,数组的内容以逗号连接字符串,然后再进行运算拼接操作
console.log('相加', arr1 + 4); // 1,2,34
console.log('比较全等', arr1 === '1,2,3'); // false
console.log('比较相等', arr1 == '1,2,3'); // true 【会进行隐式转换】
console.log('比较大小', arr1 > '1'); // false

valueOf场景/作用​

  • valueOf:返回该对象的原始值

  • valueOf和toString二者并存情况下

    • 数值运算优先使用valueOf

    • 字符串运算中,优先调用了toString

javascript
// 运算例子
const obj1 = {
value: 3,
};

console.log(4 + obj1);

// 打印 4[object Object]
// 问题:期望得到结果7
// 解决方案:利用valueOf方法,重写valueOf

const obj2 = {
value: 3,
valueOf: function () {
return this.value;
},
};

console.log('obj2', 4 + obj2);

// valueOf/toString 二者并存的情况下,在数值运算中,优先调用了valueOf,字符串运算中,优先调用了toString
const obj3 = {
value: 3,
valueOf: function () {
return this.value;
},
toString: function () {
return '123';
},
};

console.log(obj3 + 3); // 6
console.log(obj3 > 2); // true
console.log(String(obj3)); // 123
console.log(Number(obj3)); // 3

运算符优先级

加号运算符

两种含义

  • 用在数字上,就是相加
  • 用在字符串上,就是**字符串连接(**拼接)
text
"1" + false =>"1false"
"1" + 2 =>"12"

巧用+转换为整数

text
+"1"
=>1
+"a"
=>NAN
+new Date()
=>1626963213772

相等运算符

1、如果有一个操作数是布尔值,则在比较相等性之前先将其转换为数值——false转换为0,而true转换为1;

js
'true'==true // 等价于'true' == 1 返回false

2、如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值;

3、如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法,用得到的基本类型值按照前面的规则进行比较;

其次,NaN与任何值都不相等,包括NaN本身。

js
const a = new Number(1) // a 是Number对象
const b = 2
a == b // true, 会进行类型转换,在进行运算符比较时候,如果其中是对象,会把对象转换为原始类型
a === b // false

0.1 + 0.2 != 0.3理解

参考:https://github.com/febobo/web-interview/issues/82

简单来说: 由于计算机无法对十进制进行运算 ,需要转换二进制,十进制小数0.1和0.2在转换二进制过程中出现无限循环,造成了精度丢失

运算时发生了什么?​

  • 计算机无法直接对十进制的数字进行运算 【硬件物理特性已经决定的】
    • 会先转换成二进制,进行转换运算
  • 大多数小数的二进制都是无限循环的
javascript
0.1和0.2转换成二进制后会无限循环
0.1 -> 0.0001100110011001...(无限循环)
0.2 -> 0.0011001100110011...(无限循环)

小数是如何转二进制的?​

  • 小数转换成二进制小数采用" 乘2取整,顺序排列" 法
  • 十进制整数转换为二进制整数采用" 除2取余,逆序排列 "法

详细步骤:

  1. 将小数部分乘以2,将结果的整数部分写入二进制的第一位。如果结果为整数,则小数部分为0。如果结果不为整数,则将其小数部分作为新的小数部分,继续乘以2,重复此步骤,直到结果为0或达到所需的位数。
  2. 将整数部分转换为二进制,从最高位开始写入。如果整数部分为0,则在二进制中写入0。
  3. 将小数部分转换为二进制,从小数点后第一位开始写入,直到达到所需的位数。如果小数部分为0,则在二进制中写入0。
  4. 将整数部分和小数部分合并在一起,中间用小数点隔开,即可得到小数的二进制表示。

例如,将0.625转换为二进制:

  1. 0.625 × 2 = 1.25,整数部分为1,小数部分为0.25。
  2. 整数部分1的二进制表示为1。
  3. 小数部分0.25 × 2 = 0.5,整数部分为0,小数部分为0.5。
  4. 小数部分0.5 × 2 = 1,整数部分为1,小数部分为0。此时已经得到3位小数的二进制表示。
  5. 将整数部分1和小数部分10合并在一起,中间用小数点隔开,得到0.101的二进制表示。

二进制是如何计算的?​

在二进制中,每个位(从右到左)代表一个2的幂,其指数与位数相同

如:10101110

  • 得到174,即十进制为174
text
1*2^7 + 0*2^6 + 1*2^5 + 0*2^4 + 1*2^3 + 1*2^2 + 1*2^1 + 0*2^0

二进制是如何存储的?​

在计算机内存中,每个二进制位都代表一个0或1,8位二进制称为一个字节

  • 整数和小数都可以使用二进制来表示。在计算机中,负数通常使用补码表示。补码是将数值取反并加上1得到的值。例如,-5的补码为11111011。

  • 计算机中,二进制数通常是以补码的形式存储的。

js十进制转换二进制是如何存储的

  • 在将十进制数转换为二进制数时,JavaScript 会将结果以双精度浮点数的形式存储
  • 这种双精度浮点数采用 IEEE 754 标准定义的 64 位二进制格式,其中包括符号位、指数位和尾数位

js中数值类型是如何存储的?​

JavaScript 中的数字类型(Number)可以表示整数和浮点数,存储方式是使用 IEEE 754 标准定义的双精度浮点数格式

  • 在 JavaScript 中,所有数字类型的值都是使用 64 位二进制格式表示的,其中包括符号位、指数位和尾数位。这种表示方式可以支持非常大和非常小的数字

科学计算法如何表示?​

科学计数法是一种用于表示非常大或非常小的数字的方法

  • 在计算机中,科学计数法通常使用浮点数表示。浮点数由一个符号位、一个指数和一个尾数组成,其中指数部分表示10的幂。在JavaScript中,使用科学计数法可以通过在数字后面添加E或e以及指数来表示。例如,1e6表示10的6次方,即1000000。

javaScript对数值存储的 存储方式是双精度浮点数,其长度为8个字节,即64位比特

64位比特又可分为三个部分:

  • 符号位S:第 1 位是正负数符号位(sign),0代表正数,1代表负数
  • 指数位E:中间的 11 位存储指数(exponent),用来表示次方数,可以为正负数。在双精度浮点数中,指数的固定偏移量为1023
  • 尾数位M:最后的 52 位是尾数(mantissa),超出的部分自动进一舍零

0.1和0.2,转换二进制,科学计算法如何表示?

0.1和0.2的二进制表示如下:

0.1的二进制表示为0.0001100110011001100110011001100110011001100110011...(循环) 0.2的二进制表示为0.001100110011001100110011001100110011001100110011...(循环)

可以看出,这些数字在二进制中是无限循环的。因此,在计算机中,这些数字的浮点数表示可能会有精度问题,因为在浮点数中只能存储有限的位数

对于科学计数法,0.1和0.2的科学计数法表示为:

0.1 = 1.1001100110011001100110011001100110011001100110011 × 2^(-4) 0.2 = 1.1001100110011001100110011001100110011001100110011 × 2^(-3)

其中,指数部分为负数,表示小数点向左移动的位数。在JavaScript中,可以使用科学计数法来表示这些数字,例如:

javascript
1.1e-4 // 表示0.1的科学计数法
1.1e-3 // 表示0.2的科学计数法

计算机中,浮点数的表示方式有限,因此使用科学计数法来表示数字时仍然可能会出现舍入误差。

第三方库​

解决方案:

  • 1、number-precision
  • 2、mathjs

0.1+0.3==0.4​

  • 0.1 + 0.3 在转换二进制的时候也有精度损失,只是2者刚好抵消了彼此的误差

精度丢失是js的问题吗?​

  • 精度丢失不是js的问题,而是Number采用的是IEEE754规范中64位双精度浮点数编码, IEEE 754 标准存储位有限

如何解决精度丢失问题​

  • 1、转换为整数运算,整数不会造成精度丢失

    • 进行整数运算操作。在最后的结果中,再将整数转换回所需的精确数

    • 判断小数的位数,乘以一定的倍数

    • 计算结果之后,再转换为原来的精确数

  • 3、第三方库Decimal.js 、Math.js、number-precision

  • 4、设置误差值:Number.EPSILON

    • 可以通过Number.EPSILON设置误差值来解决

    • 如 只要判断 0.1 + 0.2 - 0.3 是否小于Number.EPSILON,如果小于,则 0.1 + 0.2 == 0.3

      js
      function isEqual(a, b) {
      return Math.abs(a - b) < Number.EPSILON
      }

toFIxed理解

例子:

(2.45).toFixed(1) 输出 2.5、(2.55).toFixed(1) 输出也是2.5,解释输出结果?

  • toFixed() 方法的具体执行过程可以简单理解为先将数字转化为字符串,然后再对转化后的字符串进行四舍五入,最后返回一个字符串类型的结果

理解:toFixed是转换指定位数之后,再四舍五入

对于 (2.45).toFixed(1) 这个例子,2.45 转化为字符串后小数点后一位为 4,小于 5,所以结果为 2.4。而对于 (2.55).toFixed(1) 这个例子,2.55 转化为字符串后小数点后一位为 5,大于等于 5,所以结果为 2.6。但是由于指定了保留一位小数,所以结果都只显示一个小数位,即 2.4 和 2.6 都被四舍五入为了 2.5。

当使用 toFixed() 方法时,如果原始数字小数部分的第一位是 5,那么舍入规则会基于第二位数字的奇偶性来进行处理,这被称为“银行家舍入规则”。

具体来说,如果第二位数字为偶数,则将第一位数字舍去;如果第二位数字为奇数,则将第一位数字进一位。这种舍入规则旨在消除数字的偏差,从而使得大量数字的舍入后结果更加公平和准确。

在本例中,当使用 toFixed(1) 对 2.45 进行舍入时,转换后的结果为字符串 "2.4",其中小数点后第一位为 4。因为第二位是 5,按照银行家舍入规则,应该向上进一位,所以结果为 2.5。

银行家舍入规则

  • 银行家舍入规则是一种常见的数字舍入规则,旨在消除数字舍入带来的偏差。它的规则是:如果需要舍入的数字小数点后第一位为 5,那么舍入规则会基于第二位数字的奇偶性来进行处理。如果第二位数字是偶数,则将第一位数字舍去;如果第二位数字是奇数,则将第一位数字进一位。
  • toFixed() 方法的舍入规则就是参考了银行家舍入规则。当调用 toFixed() 方法并传入保留的小数位数时,它会将数字舍入到指定的小数位数,并按照银行家舍入规则进行舍入。如果需要舍入的数字小数点后第一位为 5,toFixed() 方法将基于第二位数字的奇偶性来进行处理。