JS之金额(金融相关)字符串格式化处理工具

155 阅读2分钟

金额处理方法:千分位处理,保留n位小数且只舍不入

比如 0.1+0.2=0.1+0.2=0.30000000000000004 这种问题怎么确保进度?


前言

  • 我们在处理金额数据的时候经常会需要对其进行“千分位分隔”和“保留小数位”处理,但是如果只是简单的使用 toLocalString 和 toFixed 的话是容易造成一些问题的
  • 比如 toLocalString 存在一定的兼容性问题,在我们通过一些混合框架开发移动端应用的时候就可能无效,toFixed 的话则存在进一位的情况,这是因为toFixed是一个四舍六入五成双的方法
  • toFixed是一个四舍六入五成双的方法中,"五成双"指的是根据5后面的数字来定,当5后有数时,舍5入1;当5后无有效数字时,需要分两种情况来讲:① 5前为奇数,舍5入1;② 5前为偶数,舍5不进。(0是偶数)
  • 而我们对金额进行格式化处理时,除了“千分位分隔”之外,在“保留小数位”时需求是进行直接舍去多余小数而不进一
  • 以下便是我提供的一种处理方式

代码在此!!


/**
 * 判断是否为数值
 * @param {*} str
 * @returns
 */
export function isNumber(str) {
  if (typeof str === 'number') {
    return true
  }

  if (typeof str !== 'string') {
    return false
  }

  return !isNaN(str) && !isNaN(parseFloat(str))
}

/**
 * 转数值
 * @param {*} str
 * @returns
 */
export function toNumber(str) {
  return isNumber(str) ? Number(str) : 0
}

/**
 * 求和函数
 * @param {*} arr
 * @param {*} property
 * @param {*} digit 用于控制精度,即每次求和计算时截取的小数位
 * @returns
 */
export function calcSum(arr, property, _digit = 2) {
  // 用于避免求和精度问题,比如 0.1+0.2=0.30000000000000004
  function formatFloat(f, digit = _digit) {
    var m = Math.pow(10, digit)
    return parseInt(f * m, 10) / m
  }

  if (!property) {
    return arr.reduce((a, b) => formatFloat(toNumber(a) + toNumber(b)), 0)
  }
  return arr.reduce(
    (total, item) => formatFloat(toNumber(total) + toNumber(item[property])),
    0
  )
}

/**
 * 数值类型格式化(这里不用toFixed,因为toFixed会存在进一位的情况)
 * @param {*} num
 * @param {*} fractionDigits
 * @returns
 */
export function fixedNumber(_num, fractionDigits = 2) {
  const num = String(_num)
  // 处理非数字
  if (!isNumber(num)) {
    return Number(0).toFixed(fractionDigits)
  }

  // 小数点处理
  let numStr = num.replace(/\.\d*$/g, function(m) {
    return m.slice(0, fractionDigits + 1)
  })

  // 小数位不足,则补零
  const amountAry = numStr.split('.')
  if (amountAry.length > 1 && amountAry[1].length < fractionDigits) {
    amountAry[1] = amountAry[1].padEnd(fractionDigits, '0')
  } else if (amountAry.length < fractionDigits) {
    amountAry[1] = '0'.repeat(fractionDigits)
  }
  numStr = fractionDigits < 1 ? amountAry[0] : amountAry.join('.')

  return numStr
}

/**
 * 金额格式化
 * @param {*} amount
 * @returns
 */
export function formatAmount(_amount, fractionDigits = 2) {
  let amount = fixedNumber(_amount, fractionDigits)
  // 处理非数字
  if (!isNumber(amount)) {
    return Number(0).toFixed(fractionDigits)
  }

  // 千分位处理
  amount = !(amount + '').includes('.')
    ? // 1-3位后面一定要匹配3位
    (amount + '').replace(/\d{1,3}(?=(\d{3})+$)/g, (match) => {
      return match + ','
    })
    : (amount + '').replace(/\d{1,3}(?=(\d{3})+(\.))/g, (match) => {
      return match + ','
    })
  // 小数点处理
  return amount
}

实测(使用案例):

  • 金额求和测试
let mockData = [{cost:0.01}, {cost:0.02}, {cost:9999.82}, {cost:6699.120090213}, {cost:6699.25}]

// --
console.log('数值求和: ', 0.01+0.02+9999.82+6699.120090213+6699.25)
console.log('reduce对象求和: ', mockData.reduce((total,item) => total+item.cost,0))
console.log('calcSum 函数测试 求和: ', calcSum(mockData,'cost'))
console.log('fixedNumber+calcSum 函数测试 求和: ', fixedNumber(calcSum(mockData,'cost')))
console.log('formatAmount+calcSum 函数测试 求和: ', formatAmount(calcSum(mockData,'cost')))

输出:

image.png