0.1 + 0.2 !== 0.3
- 所有的值在计算机中都是按照二进制存储的,运算也是基于二进制来的
- 证书运算一般不会出现问题,出安全数范围外除外
- 小数运算一般会出现问题
- 浮点数转为二进制会出现"无限"循环的情况,计算机底层存储的时候按照可以识别的最长位数进行存储,直接干掉...所以浮点数存储的二进制本身的失去精准度了
- 所以最后运算也是失精准度的,而小数最后面全是0会去掉
// 扫盲: 把十进制转为二进制
// 整数: 除2取余,最后倒着拼接
// 小数: × 2, 取整一直到结果是1为止
// => [10进制value].toString(2)
0.1十进制 -> 2进制
0.1 * 2 = 0.2 0
0.2 * 2 = 0.4 0
0.4 * 2 = 0.8 0
0.8 * 2 = 1.6 1
0.6 * 2 = 1.2 1
0.2 * 2 = 0.4 0
...
// => 没有任何情况下为1
运算保证精准度,实现思路: 把小数变为整数(乘以系数)运算,结果再除以系数
// => 计算系数
const coefficient = function (num) {
num = String(num);
let [,char=''] = num.split('.');
let len = char.length;
return Math.pow(10, len);
}
const plus = function (n, m) {
n = Number(n)
m = Number(m)
// => 是否是正常number
if (isNaN(n) || isNaN(m)) return NaN;
// => 求最大系数
let coffic = Math.max(coefficient(n), coefficient(m));
return (n * coffic + m * coffic) / coffic;
}
plus(0.1, 0.2);// 0.3
js中的数据转换
- 隐式转换(浏览器私底下转换的)和显示转换
Number([val])
- 一般用于浏览器的隐式转换中
- 数学运算
- isNaN检测
- == 比较
- 规则:
- 字符串转数字: 空字符串转为0, 如果出现任何的非有效数字字符,结果都是NaN
- 把布尔转为数字: true -> 1 false -> 0
- null -> 0 undefined -> NaN
- Symbol无法转为数字,会爆错 Uncaught TypeError: Cannot convert a Symbol value to a number
- BigInt 去除"n" (超过安全数字的,会按照科学计数法处理)
- 把对象转为数字:
- 先看是否有Symbol.toPrimitive 这个发方法, 有调用,如果不存在
- 再调用 valueOf 获取原始值, 如果获取的值不是原始值
- 再调用对象 toString 把其变为字符串
- 再把字符串用Number方法转为数字
// => 一般的情况
Number('')
// => 0
Number(null)
// => 0
Number(undefined)
// => NaN
Number(Symbol())
// => Uncaught TypeError: Cannot convert a Symbol value to a number
Number(BigInt(123))
// => 123
Number(true)
// => 1
Number(false)
// => 0
- Number对象
单元素数字数组
// => [10]
Number([10])// => 10
// => 流程如下
[10][Symbol.toPrimitive]
// => undefined
[10].valueOf()
// => [10]
[10].toString()
// => '10'
Number('10')
// => 10
多元素数字数组
// => [20, 30]
Number([10, 20]) // => NaN
// => 流程如下
[10, 20][Symbol.toPrimitive]
// => undefined
[10, 20].valueOf()
// => [10, 20]
[10, 20].toString()
// => '10,20'
Number('10,20')
// => NaN
- 普通对象
let obj = {0 : 10, length: 1}
Number(obj) // => NaN
// => 流程如下
obj[Symbol.toPrimitive]
// => undefined
obj.valueOf()
// => {0: 10, length: 1}
obj.toString()
// => '[object Object]'
Number('[object Object]')
// => NaN
- 转化Date对象
let time = new Date()
Number(time) // => 1646574101787
// => 流程如下
time[Symbol.toPrimitive]
// => ƒ [Symbol.toPrimitive]() { [native code] } 存在
time[Symbol.toPrimitive]('number')
// => 1646574101787
time[Symbol.toPrimitive]('default')
// => 'Sun Mar 06 2022 21:41:41 GMT+0800 (中国标准时间)'
time[Symbol.toPrimitive]('string')
// => 'Sun Mar 06 2022 21:41:41 GMT+0800 (中国标准时间)'
//=> 这个方法可以传三个参数 'number/string/default' => 除了Number(对象)按照这个流程
// => String(对象) 把对象变成字符串也按照这个规则处理,如果存在Symbol.toPrimitive,则执行传递的是'string'
ParseInt([val], [redix]) parseFloat([val])
- 一般用于手动转换
- 规则: [val]必须是一个字符串,如果不是字符串;然后从字符串左侧第一个字符开始查找,把找到的有效数字获取出来,遇到非有效数字停止查找;parseFloat可以多识别一个小数点
- redix: 把找到的内容当作[redix]进制值转为10进制
- redix:无值时默认是10进制:但是'0x'开头的默认是16进制
- redix: 取值范围[2-36],不在范围内的一般处理结果都是NaN(个别看浏览器)
parseInt(123, 3) // => 5
// 3进制没有3,所以走如下
// => 1*3^1 + 2*3^0 => 3 + 2 = 5
parseInt('0x1236') // => 4662
// => 1 * 16^3 + 2 * 16^2 + 3 * 16^1 + 6 * 16^0 => 4096 + 512 + 48 + 6 => 4662
鄙视题
let arr = [27.2, 0, '0013', 123]
arr = arr.map(parseInt)// => [27, NaN, 1, 5]
// => 解析如下
parseInt(27.2, 0)
// => 27
parseInt(0, 1)
// => NaN
parseInt('0013', 2)
// => 1
parseInt(123, 3)
// => 5