一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情。
概述
我们的JS使用IEEE 754标准定义的64位浮点数格式表达数值,
也就是说在JS中想要表达数值范围在-2^53到2^53之间的所有整数,精度是没有问题的, 一旦数值超出这个范围,在数值末尾就会损失一定的精度
所以, 当我们计算两个数值相加, 而这两个数值之和超过JS能表示的最大安全数的情况下,就会存在精度丢失的问题
这也是为什么ES6 中引入BigInt的原因
大数相加:
示例
控制台中打印 console.log(12 + 12) , 此时可以得到 24, 是完全没有问题的。
JS能表达的最大安全整数Number.MAX_SAFE_INTEGER -> 9007199254740991
我们在控制到打印console.log(Number.MAX_SAFE_INTEGER + 12), 会得到这样的结果:
可以看出最后一位 1 + 2 是不可能等于4的, 说明此时就出现了精度丢失的问题
解决方案:
我们可以通过遍历数字的每一项, 让每一项去相加, 最后把每一项的到的结果结合起来, 就能达到我们想要的结果
但是数字是不能遍历的,所以最快捷的方法就是把他们转换成字符串, 然后去实现第一步 -> 遍历
确定基本思路之后,开始写代码
function bigSum(a, b){
//接收到的是数字,先将他们转换成字符串
a = '' + a
b = '' + b
//假如有一个为0, 直接返回不为0的一个
if(a === '0' || b === '0') return a === '0' ? b : a
//获得a和b 较长的一个,以为较短的一个前面补0,直到a和b长度相等
const len = Math.max(a.length, b.length)
a = a.padStart(len, '0'); // 000123
b = b.padStart(len, '0'); // 123456
//下面会用到的变量
let t = 0, //每次相加的结果
f = 0, //是否进位
sum = '' //最后的和
//从后向前遍历
for(let i = len - 1; i >= 0; i--){
//此次运算的结果 + 上次是否有进位
t = parseInt(a[i]) + parseInt(b[i]) + f
//如果此次结果大于10, 说明下次可以进一位
//f只可能是1 或 0
f = Math.floor(t / 10)
//这一次加的永远是这次的个位, 如果此次结果>10, 会在下次进一位
sum = t% 10 + sum
}
//如果遍历结束,发现f == 1 表示仍然可以进位, 就在sum最前面+1
if(f){
sum = f + sum
}
return sum
}
此时来测试一下我们的代码
let a = Number.MAX_SAFE_INTEGER, //9007199254740991
b = 12
console.log(bigSum(a, b))
输出结果:
大数相乘
既然相加会有精度丢失的问题, 那么想乘同样也会存在这样的问题。
使用同样的思路, 转换成字符串解决一下。
其实这个是leetcode上的一道经典题目:
解题思路
假设我们要计算 123 * 45, 我们的历程是这样的:
\
- 先用个位的5 去和 3 2 1 相乘
- 再用十位的4 去的 3 2 1 相乘
- 最后错位相加, 得到计算结果
再细化一下就变成这样:
同样是错位相加
新建一个数组res去接收, 每次相加的值, 是否可以得到这样的规律
num1[i] 和num2[j]的乘积对应的就是 res[i + j] 和 res[i + j + 1] 这两个位置
我们将res的每一项上的内容相加, 是不是就可以得到想要的结果了呢?
转换成代码,这样写:
function multiply(num1, num2){
//首先转换成字符串
num1 = '' + num1
num2 = '' + num2
if(num1 === '0' || num2 === '0') return '0'
if(num1 === '1' || num2 === '1') return num1 === '1' ? num2 : num1
let n1 = num1.length,
n2 = num2.length
//存储结果的数组 最长为n1 + n2
let res = new Array(n1 + n2).fill(0)
for(let i = n1 - 1; i >= 0; i--){
for(let j = n2 - 1; j >= 0; j--){
let mul = (+num1[i]) * (+num2[j]), //此时遍历到的两数之积
p1 = i + j,
p2 = i + j + 1,
sum = mul + res[p2] //第一次res[p2]一定是0, 加的是上一次如果有超过10的部分, 上一次的十位加在这一次的个位上
//后面的位置
res[p2] = sum % 10
//sum如果有超过10的部分就加到上一位中, 下次进来, p1就会到p2的位置
res[p1] += parseInt(sum / 10)
}
}
//找到res中前面所有剩余的0
let i = 0
while(i < res.length && res[i] == 0){
i++
}
//转换结果
let str = ''
while(i < res.length){
str += res[i]
i++
}
return str
}
参考资料: