下面是处理JavaScript金额计算精度问题的详细解决方案:
- 基础工具类实现:
class DecimalCalculator {
// 获取小数位数
static getDecimalLength(num) {
const decimal = String(num).split('.')[1]
return decimal ? decimal.length : 0
}
// 将小数转为整数
static toInteger(num) {
return Number(num.toString().replace('.', ''))
}
// 加法
static add(num1, num2) {
const maxDecimal = Math.max(
this.getDecimalLength(num1),
this.getDecimalLength(num2)
)
const base = Math.pow(10, maxDecimal)
return (num1 * base + num2 * base) / base
}
// 减法
static subtract(num1, num2) {
const maxDecimal = Math.max(
this.getDecimalLength(num1),
this.getDecimalLength(num2)
)
const base = Math.pow(10, maxDecimal)
return (num1 * base - num2 * base) / base
}
// 乘法
static multiply(num1, num2) {
const decimalLength1 = this.getDecimalLength(num1)
const decimalLength2 = this.getDecimalLength(num2)
const base = Math.pow(10, decimalLength1 + decimalLength2)
return (this.toInteger(num1) * this.toInteger(num2)) / base
}
// 除法
static divide(num1, num2) {
const decimalLength1 = this.getDecimalLength(num1)
const decimalLength2 = this.getDecimalLength(num2)
const base1 = this.toInteger(num1)
const base2 = this.toInteger(num2)
return (base1 / base2) * Math.pow(10, decimalLength2 - decimalLength1)
}
// 四舍五入到指定小数位
static round(num, decimal = 2) {
const base = Math.pow(10, decimal)
return Math.round(num * base) / base
}
}
- 完整的金额处理工具类:
class MoneyUtil {
// 金额格式化
static format(amount, options = {}) {
const {
decimal = 2,
separator = ',',
symbol = '¥',
precision = true
} = options
let num = precision ? DecimalCalculator.round(amount, decimal) : amount
const [integer, fraction = ''] = String(num).split('.')
const formatted = integer.replace(/\B(?=(\d{3})+(?!\d))/g, separator)
const decimalPart = fraction.padEnd(decimal, '0')
return `${symbol}${formatted}${decimal ? '.' + decimalPart : ''}`
}
// 金额转中文大写
static toChinese(amount) {
const numbers = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']
const units = ['', '拾', '佰', '仟', '万', '拾', '佰', '仟', '亿']
const decimal = ['角', '分']
let [integer, fraction = ''] = String(amount).split('.')
integer = parseInt(integer).toString()
let result = ''
// 处理整数部分
for (let i = 0; i < integer.length; i++) {
const num = parseInt(integer[i])
const unit = units[integer.length - 1 - i]
if (num === 0) {
if (i === integer.length - 1 || result.slice(-1) !== numbers[0]) {
result += numbers[0]
}
} else {
result += numbers[num] + unit
}
}
result += '元'
// 处理小数部分
if (fraction) {
for (let i = 0; i < Math.min(2, fraction.length); i++) {
const num = parseInt(fraction[i])
if (num !== 0) {
result += numbers[num] + decimal[i]
}
}
}
if (!fraction) {
result += '整'
}
return result.replace(/零+/g, '零').replace(/零元/, '元')
}
// 金额分转元
static fenToYuan(fen) {
return DecimalCalculator.divide(fen, 100)
}
// 金额元转分
static yuanToFen(yuan) {
return DecimalCalculator.multiply(yuan, 100)
}
// 计算折扣金额
static calculateDiscount(amount, discount) {
return DecimalCalculator.multiply(
amount,
DecimalCalculator.divide(discount, 100)
)
}
// 计算税额
static calculateTax(amount, rate) {
return DecimalCalculator.multiply(
amount,
DecimalCalculator.divide(rate, 100)
)
}
}
- 使用示例:
// 基础运算
const price = 19.99
const quantity = 2.5
const discount = 0.3
const total = DecimalCalculator.multiply(price, quantity) // 49.975
const discountAmount = DecimalCalculator.multiply(total, discount) // 14.9925
const finalAmount = DecimalCalculator.subtract(total, discountAmount) // 34.9825
const roundedAmount = DecimalCalculator.round(finalAmount) // 34.98
// 金额格式化
MoneyUtil.format(34.98) // "¥34.98"
MoneyUtil.format(1234567.89, { separator: ',', symbol: '$' }) // "$1,234,567.89"
// 中文大写
MoneyUtil.toChinese(3456.78) // "叁仟肆佰伍拾陆元柒角捌分"
// 分转元
MoneyUtil.fenToYuan(10050) // 100.50
// 元转分
MoneyUtil.yuanToFen(100.50) // 10050
// 折扣计算
MoneyUtil.calculateDiscount(100, 20) // 20(20%折扣)
- 实际应用场景:
// 购物车计算
class ShoppingCart {
constructor() {
this.items = []
}
addItem(item) {
const { price, quantity } = item
const amount = DecimalCalculator.multiply(price, quantity)
this.items.push({ ...item, amount })
}
getSubtotal() {
return this.items.reduce((sum, item) => {
return DecimalCalculator.add(sum, item.amount)
}, 0)
}
getTotal(discount = 0) {
const subtotal = this.getSubtotal()
const discountAmount = MoneyUtil.calculateDiscount(subtotal, discount)
return DecimalCalculator.subtract(subtotal, discountAmount)
}
}
// 发票计算
class Invoice {
constructor(amount) {
this.amount = amount
this.taxRate = 6 // 6% VAT
}
getTaxAmount() {
return MoneyUtil.calculateTax(this.amount, this.taxRate)
}
getTotal() {
return DecimalCalculator.add(this.amount, this.getTaxAmount())
}
format() {
return {
amount: MoneyUtil.format(this.amount),
tax: MoneyUtil.format(this.getTaxAmount()),
total: MoneyUtil.format(this.getTotal()),
chineseAmount: MoneyUtil.toChinese(this.getTotal())
}
}
}
- 表单验证:
// 金额输入验证指令
const moneyDirective = {
mounted(el, binding) {
const input = el.tagName === 'INPUT' ? el : el.querySelector('input')
if (!input) return
const options = binding.value || {}
const {
decimal = 2,
min = 0,
max = Number.MAX_SAFE_INTEGER
} = options
input.addEventListener('input', (e) => {
let value = e.target.value
.replace(/[^\d.]/g, '')
.replace(/^0+(\d)/, '$1')
.replace(/^\./, '0.')
.replace(/(\..*)\./g, '$1')
// 限制小数位数
const parts = value.split('.')
if (parts[1] && parts[1].length > decimal) {
parts[1] = parts[1].substring(0, decimal)
value = parts.join('.')
}
// 限制数值范围
const num = parseFloat(value)
if (num < min) value = min
if (num > max) value = max
e.target.value = value
})
}
}
// 使用方式
<input v-money="{ decimal: 2, min: 0, max: 9999.99 }" />
- 常见错误处理:
class MoneyError extends Error {
constructor(message, amount) {
super(message)
this.name = 'MoneyError'
this.amount = amount
}
}
function validateAmount(amount) {
if (typeof amount !== 'number') {
throw new MoneyError('金额必须是数字类型', amount)
}
if (isNaN(amount)) {
throw new MoneyError('金额不能为NaN', amount)
}
if (!isFinite(amount)) {
throw new MoneyError('金额必须是有限数字', amount)
}
if (amount < 0) {
throw new MoneyError('金额不能为负数', amount)
}
}
// 使用示例
try {
validateAmount(-100)
} catch (error) {
if (error instanceof MoneyError) {
console.error(`金额错误: ${error.message}, 值: ${error.amount}`)
}
}
这个解决方案提供了:
- 精确的数学运算
- 金额格式化
- 中文大写转换
- 分转元和元转分转换
- 折扣和税额计算
- 实际应用场景示例
- 表单验证
- 错误处理
这些工具可以帮助避免JavaScript中的浮点数精度问题,并提供完整的金额处理方案。
评论区