类型转换/运算规则
运算规则
- 一个布尔值和一个对象进行比较时,会将这两个值转换为数字进行最后的比较
- 注意:全等比较的时候不会进行类型转换
例题:
为什么“false == []”和“false == ![]”都返回true?
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方法【隐式转换】,然后才进行操作
// 字符串使用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
-
// 运算例子
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
运算符优先级
加号运算符
两种含义
- 用在数字上,就是相加
- 用在字符串上,就是**字符串连接(**拼接)
"1" + false =>"1false"
"1" + 2 =>"12"
巧用+转换为整数
+"1"
=>1
+"a"
=>NAN
+new Date()
=>1626963213772
相等运算符
1、如果有一个操作数是布尔值,则在比较相等性之前先将其转换为数值——false转换为0,而true转换为1;
'true'==true // 等价于'true' == 1 返回false
2、如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值;
3、如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法,用得到的基本类型值按照前面的规则进行比较;
其次,NaN与任何值都不相等,包括NaN本身。
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在转换二进制过程中出现无限循环,造成了精度丢失
运算时发生了什么?
- 计算机无法直接对十进制的数字进行运算 【硬件物理特性已经决定的】
- 会先转换成二进制,进行转换运算
- 大多数小数的二进制都是无限循环的
0.1和0.2转换成二进制后会无限循环
0.1 -> 0.0001100110011001...(无限循环)
0.2 -> 0.0011001100110011...(无限循环)
小数是如何转二进制的?
- 小数转换成二进制小数采用" 乘2取整,顺序排列" 法
- 十进制整数转换为二进制整数采用" 除2取余,逆序排列 "法
详细步骤:
- 将小数部分乘以2,将结果的整数部分写入二进制的第一位。如果结果为整数,则小数部分为0。如果结果不为整数,则将其小数部分作为新的小数部分,继续乘以2,重复此步骤,直到结果为0或达到所需的位数。
- 将整数部分转换为二进制,从最高位开始写入。如果整数部分为0,则在二进制中写入0。
- 将小数部分转换为二进制,从小数点后第一位开始写入,直到达到所需的位数。如果小数部分为0,则在二进制中写入0。
- 将整数部分和小数部分合并在一起,中间用小数点隔开,即可得到小数的二进制表示。
例如,将0.625转换为二进制:
- 0.625 × 2 = 1.25,整数部分为1,小数部分为0.25。
- 整数部分1的二进制表示为1。
- 小数部分0.25 × 2 = 0.5,整数部分为0,小数部分为0.5。
- 小数部分0.5 × 2 = 1,整数部分为1,小数部分为0。此时已经得到3位小数的二进制表示。
- 将整数部分1和小数部分10合并在一起,中间用小数点隔开,得到0.101的二进制表示。
二进制是如何计算的?
在二进制中,每个位(从右到左)代表一个2的幂,其指数与位数相同
如:10101110
- 得到174,即十进制为174
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中,可以使用科学计数法来表示这些数字,例如:
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
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()方法将基于第二位数字的奇偶性来进行处理。