解决精度丢失问题的常用方法的封装

解决精度丢失最常用的方法是将浮点数转成整数计算


/**
 * 尝试将各种类型的输入转换为 number 类型
 * @param value 值
 * @param defaultValue 默认值
 * @returns 返回value转成number类型后的数据
 */
export function toNumber(value: any, defaultValue: number = 0): number {
  // 如果输入已经是数字,直接返回
  if (typeof value === 'number') {
    return value
  }

  // 尝试将字符串转换为数字
  if (typeof value === 'string') {
    const parsed = parseFloat(value)
    return isNaN(parsed) ? defaultValue : parsed
  }

  // 将布尔值转换为数字(true -> 1, false -> 0)
  if (typeof value === 'boolean') {
    return value ? 1 : 0
  }

  return defaultValue
}

/**
 * 重写四舍五入方法
 * @param number {number} 数值
 * @param places {number} 小数位数
 * @returns 返回满足小数位数的数值
 * 例如:
 *  141.575 -> 141.58
 *  2.0 -> 2
 */
export function toFixed(number: number, places: number = 2): string {
  // 如果为NaN抛出异常
  if (isNaN(number)) return ''

  // 不需要小数位,直接四舍五入
  if (places === 0) {
    return Math.round(number).toString()
  }

  // 实际小数位数
  const actualDecimal = number.toString().split('.')[1]
  const actualDecimalLen = actualDecimal ? actualDecimal.length : 0

  // 存在小数
  let fixNum = ''
  if (actualDecimalLen) {
    // 求10的平方:实际小数位数
    const actualDecimalN = Math.pow(10, actualDecimalLen)

    // 求10的平方:实际小数位数与最终要求的小数位数差
    const diffPlaces = Math.pow(10, actualDecimalLen - places)

    // 求10的平方:最终要求的小数
    const placesN = Math.pow(10, places)

    // 数值进行四舍五入计算
    // number * actualDecimalN 将小数全部转为整数 是为了避免精度丢失 141.575 * 100 会精度丢失
    fixNum = (Math.round((number * actualDecimalN) / diffPlaces) / placesN).toString()
  } else {
    fixNum = number.toString()
  }

  // 小数位数不足时,补全
  // if (places !== 0 && fixNum.indexOf('.') === -1) {
  //   fixNum += '.'
  // }
  // if (places !== 0) {
  //   while (fixNum.split('.')[1].length < places) {
  //     fixNum += '0'
  //   }
  // }
  return fixNum
}

/**
 * 加法,解决精度丢失 0.1 + 0.2 = 0.30000000000000004
 * @param num1 数值1
 * @param num2 数值2
 * @returns
 * 例子: add(0.1, 0.2)  返回 0.3
 */
export const plus = (num1: number, num2: number) => {
  let r1, r2
  try {
    r1 = num1.toString().split('.')[1].length
  } catch (e) {
    r1 = 0
  }

  try {
    r2 = num2.toString().split('.')[1].length
  } catch (e) {
    r2 = 0
  }

  const m = Math.pow(10, Math.max(r1, r2))

  return divide(Math.round(multiply(num1, m) + multiply(num2, m)), m)
}

/**
 * 减法,解决精度丢失 0.3 - 0.1 = 0.19999999999999998
 * @param num1 数值1
 * @param num2 被减的数值
 * @returns
 * 例子: sub(0.3, 0.1)  返回 0.2
 */
export const subtract = (num1: number, num2: number) => {
  return plus(num1, -num2)
}

/**
 * 乘法,解决精度丢失 0.00035 * 100 = 0.034999999999999996
 * @param num1 数值1
 * @param num2 被减的数值
 * @returns
 * 例子: sub(0.00035, 100)  返回 0.035
 */
export const multiply = (num1: number, num2: number) => {
  let m = 0
  const s1 = num1.toString()
  const s2 = num2.toString()
  try {
    m += s1.split('.')[1].length
  } catch (e) {
    //
  }

  try {
    m += s2.split('.')[1].length
  } catch (e) {
    //
  }

  return (Number(s1.replace('.', '')) * Number(s2.replace('.', ''))) / Math.pow(10, m)
}

/**
 * 除法,解决精度丢失 0.00035 * 100 = 0.034999999999999996
 * @param num1 数值1
 * @param num2 被减的数值
 * @returns
 * 例子: sub(0.00035, 100)  返回 0.035
 */
export const divide = (num1: number, num2: number) => {
  let t1 = 0
  let t2 = 0
  try {
    t1 = num1.toString().split('.')[1].length
  } catch (e) {
    //
  }

  try {
    t2 = num2.toString().split('.')[1].length
  } catch (e) {
    //
  }

  const r1 = Number(num1.toString().replace('.', ''))
  const r2 = Number(num2.toString().replace('.', ''))
  return multiply(r1 / r2, Math.pow(10, t2 - t1))
}

/**
 * 计算百分比,返回对应小数位数
 * 公式(num1 / num2) * 100
 * @param num1 {number}
 * @param num2 {number}
 * @param places {number} 小数位数
 */
export const toRate = (num1: number, num2: number, places: number = 0) => {
  if (!num2 && num1) {
    return toFixed(100, places)
  }

  return toFixed(multiply(divide(num1, num2), 100), places)
}

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值