【前端实战】Vue watch 里做计算时浮点精度问题及解决方案
遇到的问题:
在前端开发中,我们经常会遇到一些“动态计算后保存”的需求,比如订单、预算、发票等场景。今天就来分享一个真实案例:
背景
我们的业务场景是这样的:

-
有一个 Vue 表单
fieldFrom,里面包含三个字段:-
contractMoney合同金额 -
fieldYloeuu已开票金额 -
fieldFsjhkx未开票金额
-
我们希望:未开票金额 = 合同金额 - 已开票金额 并且在用户修改任意一个字段时,剩余金额自动更新。
于是,我们写了一个 watch:
watch: {
fieldFrom: {
handler(newValue) {
this.fieldFrom.fieldFsjhkx = this.fieldFrom.contractMoney - this.fieldFrom.fieldYloeuu
},
deep: true
}
}
看起来没问题,结果在保存到数据库时出现了问题:
经过排查发现:
-
后端表字段是
DECIMAL(15, 2),最多允许 15 位数字,小数点后 2 位。 -
我们的
watch计算后没有做小数处理,导致123.456789这种浮点数直接保存,触发报错。 -
正则、前端校验都没有问题,根本原因是 JS 浮点精度 + watch 没有处理小数。
问题分析
JS 中浮点数本身存在精度问题:
0.1 + 0.2 // 结果是 0.30000000000000004
所以在 watch 或计算属性里直接减法,可能得到很多位小数。即使我们在表单里显示 2 位小数,后台接收到的仍然是原始浮点数。
解决方案
方法一:在 watch 里做小数处理
可以使用
toFixed(2)或自定义函数,确保保存时保留两位小数:
watch: {
fieldFrom: {
handler(newValue) {
const diff = this.fieldFrom.contractMoney - this.fieldFrom.fieldYloeuu
this.fieldFrom.fieldFsjhkx = Number(diff.toFixed(2))
},
deep: true
}
}
方法二:统一用工具函数处理浮点精度,之前封装了一个类似的小工具类也能满足这个需求。(这个小的工具类之前封装好的)
/**
* 金额工具类(解决浮点数精度 + 千分位展示)
*/
export const MoneyUtils = {
/**
* 千分位格式化
* @param {number|string} value - 原始金额
* @param {number} precision - 小数位,默认 2
* @returns {string}
*/
format(value, precision = 2) {
if (value == null || value === '' || isNaN(value)) return '0.00'
const num = Number(value).toFixed(precision)
return num.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
},
/**
* 保留指定小数位
* @param {number|string} value
* @param {number} precision
* @returns {string}
*/
toFixed(value, precision = 2) {
if (value == null || value === '' || isNaN(value)) return '0.00'
return Number(value).toFixed(precision)
},
/**
* 加法(解决浮点精度问题)
*/
add(a, b) {
return this._decimalOp(a, b, 'add')
},
/**
* 减法
*/
sub(a, b) {
return this._decimalOp(a, b, 'sub')
},
/**
* 乘法
*/
mul(a, b) {
return this._decimalOp(a, b, 'mul')
},
/**
* 除法
*/
div(a, b) {
if (Number(b) === 0) return NaN
return this._decimalOp(a, b, 'div')
},
/**
* 内部通用小数运算
*/
_decimalOp(a, b, type) {
const x = Number(a) || 0
const y = Number(b) || 0
const xStr = x.toString()
const yStr = y.toString()
const xDecimal = (xStr.split('.')[1] || '').length
const yDecimal = (yStr.split('.')[1] || '').length
const base = Math.pow(10, Math.max(xDecimal, yDecimal))
switch (type) {
case 'add':
return (Math.round(x * base) + Math.round(y * base)) / base
case 'sub':
return (Math.round(x * base) - Math.round(y * base)) / base
case 'mul':
return (x * base) * (y * base) / (base * base)
case 'div':
return (x * base) / (y * base)
default:
return NaN
}
}
}
总结
-
Vue watch 做计算时一定要注意浮点精度问题。
-
toFixed+Number()是最简单的前端处理方式。 -
后端也建议做二次截断,保证数据库安全。
-
遇到“最多支持15位数字”的错误,不要先怪正则或校验,先检查浮点数小数位。
更多推荐



所有评论(0)