大数相加

172 阅读5分钟

在JS中, Number 类型使用 IEEE 754格式表示整数和浮点值(在某些语言中也叫双精度值),这也就意味着JavaScript能精确表示的数字是有限的,其可以精确到个位的最大整数是2的53次方,即9007199254740992, 如果数据大小超过了这个范围,就会导致精度缺失,造成JS无法判断大小,同样的,如果我们利用已经缺失了精度的数据去计算,那么得到的结果一样也是精度缺失了的。那么该如何解决呢? 一起来看看下面的解题思路

思路

  1. 先获取传进来的两个参数中length最大的那个并用一个变量maxLength保存起来。
  2. 根据maxLength的长度,将传进来的两个参数向前补位,举个列子:若参数a是123456789009876,参数b是12345678, 则通过补位后得到的结果将是:参数a: 123456789009876 , 参数b:000000012345678。至于如何向前补位,方法有很多,能够直接想到的方法就是将字符串转数组然后遍历。这种方法太麻烦,我们可以利用字符串提供的API方法padStart()可以非常方便的解决这个问题,关于padStart()方法会在下面介绍其使用方法。
  3. 遍历maxLength,然后依次将两个参数相同位数的值相加,如果相加的和大于等于10则需要向上一位进1,依次往复,直到遍历完成。

对于第三步可能不是很好的理解,下面来看下代码吧,我们利用代码来进一步了解它的实现思路。

代码实现

代码实现第一步

方法一:

这种方法代码量太大,可以直接看第二种方法...

function bigNumberSum(a, b) {
  const  aLen = a.length
  const  bLen = b.length
  const distance = aLen - bLen
  const maxLength = distance > 0 ? aLen : bLen
  console.log(maxLength)
}

方法二:

这个方法一行代码就可以搞定了。

function bigNumberSum(a, b) {
  const maxLength = Math.max(a.length, b.length)
  console.log(maxLength)
}

代码实现第二步

这一步就不写字符串转数组后遍历这种方法了,代码量相对于下面的来说太大了,这里我们直接使用字符串提供的padStart()方法。

字符串的 padStart() 方法 和 padEnd() 方法

这两个方法都会复制字符串,如果小于指定的长度,则会在相应的一边填充字符,直至满足条件。这两个方法的第一个参数是指定的长度,第二个参数是可选的填充字符串,默认是空格。下面来看几个例子

let str = 'hunter'

console.log(str.padStart(9))  //    hunter
console.log(str.padStart(9, '0')) // 000hunter
console.log(str.padStart(9, '.')) // ...hunter

console.log(str.padEnd(9)) // hunter   
console.log(str.padEnd(9, '0')) // hunter000
console.log(str.padEnd(9, '.')) // hunter...

OK,了解了上面的两个方法之后,我们开实现思路中的第二步,直接上代码:

function bigNumberSum(a, b) {
  const maxLength = Math.max(a.length, b.length)
  // console.log(maxLength)
  a = a.padStart(maxLength, '0')
  b = b.padStart(maxLength, '0')
  console.log(a) // 132456
  console.log(b) // 000089
}

bigNumberSum('132456', '89')

上面这两步都很好理解,我们接着看第三步

代码实现第三步

function bigNumberSum(a, b) {
  const maxLength = Math.max(a.length, b.length)

  a = a.padStart(maxLength, '0')
  b = b.padStart(maxLength, '0')

  let temp = 0 // 用来标识是否需要进位  满10进1
  let fragment = 0 // 保存相同位数相加得到的值
  let sum = ''
  for(let i = maxLength - 1; i >= 0; i--) {
    // 记录 a和b 相同位数相加的结果, 同时加上temp
    // 如果temp为1,那么说明之前的位数相加时 满10了,需要进1,那么到了这一位需要加一
    fragment = parseInt(a[i]) + parseInt(b[i]) + temp

    // 根据满10进1的原则,这里计算并保存其结果
    temp = Math.floor(fragment / 10) 
    
    // 这里的fragment % 10 是取余数,即余数是当前遍历到的位置的值,多出来的值会在下一轮循环中加上(要记住满十进一原则)
    sum = fragment % 10 + sum
  }

  // 这里要处理下,如果循环完毕后,temp的结果为1,则说明还需要再向前进一位
  if(temp === 1){
    sum = "1" + sum;
  }

  return sum 
}

bigNumberSum('9007199254740991', '1234567899999999999') // 1243575099254740990

到这里就完成了一个简单版的大数相加的工具方法。

完成版代码

function bigNumberSum(a, b) {
  const maxLength = Math.max(a.length, b.length)

  a = a.padStart(maxLength, '0')
  b = b.padStart(maxLength, '0')

  let temp = 0 
  let fragment = 0 
  let sum = ''
  for(let i = maxLength - 1; i >= 0; i--) {
    fragment = parseInt(a[i]) + parseInt(b[i]) + temp
    temp = Math.floor(fragment / 10) 
    sum = fragment % 10 + sum
  }
  if(temp === 1){
    sum = "1" + sum;
  }
  return sum 
}

总结

以上是我们实现了一个简单版的大数相加的方法,不过需要注意的地方是,传入进来的参数需要是字符串,遗憾的是(我懒)在这一版实现的代码中没有做类型判断, 如果传入了一个非常大的Number类型的数字(比2的53次方大)会出现问题,当然,其实代码里面还有很多地方需要舔砖加瓦,这里就不一一实现了。

参考链接

zhuanlan.zhihu.com/p/72179476