你绝对想不到 toFixed()明明是数字的方法,结果转出来的是字符串,转字符串也就算了,结果,值还有可能不对。
我们正常理解的四舍五入,及时见5就入。但是你看看
对比之下发现 不管是 数字类型的 1.45 还是1.35 在toFixed()后都是1.4,结果还是字符串。什么原因呢
其实这也不错,为啥----
银行家算法:四舍六入五考虑,五后非0 就进一,五后为0看奇偶,五前为偶当舍去,五后为奇要进一。
银行家算法是一种最有代表性的 避免死锁的算法。 在避免死锁方法中允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次分配资源的安全性,若分配不会导致系统进入不安全状态,则分配,否则等待。
例子如下。
所以这就造成了1.35 和1.45 在四舍五入后都是1.4 (银行家就是 5前奇进偶不进)
但是你看看toFixed(),假设总共有3块钱,我们用toFixed() 去分钱,结果就有问题。1.25和1.75 的时候就会多1毛前,如果在财务中,这1毛钱就翻天了。
好了继续说一下toFixed()(全TM是字符串),
toFixed它是一个四舍六入 五成双的诡异的方法(也叫银行家算法),
"四舍六入五成双"含义:对于位数很多的近似数,当有效位数确定后,其后面多余的数字应该舍去,只保留有效数字最末一位,这种修约(舍入)规则是“四舍六入五成双”,也即“4舍6入5凑偶”这里“四”是指≤4 时舍去,"六"是指≥6时进上,"五"指的是根据5后面的数字来定,当5后有数时,舍5入1;当5后无有效数字时,需要分两种情况来讲:①5前为奇数,舍5入1;②5前为偶数,舍5不进。(0是偶数)
但是
经过我的测试发现,在chorme下面(最新版),并没有完全遵守这个规则,尤其是5的后面没有数字的时候,不是这么判断的,如下:
继续先看例子 跟上边的测试例子有冲突
var b = 1.335
console.log(b.toFixed(2)) // "1.33" 是不是以为是1.34
var b = 1.345
console.log(b.toFixed(2)) // "1.34" 是不是以为是1.35
var b = 1.355
console.log(b.toFixed(2)) // "1.35" 是不是以为是1.36
var b = 1.365
console.log(b.toFixed(2)) // "1.36" 是不是以为是1.37
var b = 1.375
console.log(b.toFixed(2)) // "1.38"
var b = 1.385
console.log(b.toFixed(2)) // "1.39"
可以发现在chorme下没有完全去遵循这个规律,或许它有自己的算法,但是毕竟它没有遵循通用的银行家算法,所以
tofixed这个方法在涉及到金钱计算的业务中还是少用, 最好别用,否则可能会出大问题!
自己的做法就是根据 精确位数 来取小数点后的数,然后判断精确位是大于4还是小于等于4,上代码吧,不说了:
假设做做到 是两位小数,最少就是取整,不留小数
function moneySwitch(money, precision){ // precision是需要精确的位数,如百分位就是2
var result = 0;
//先进行一个千分位的四舍五入,保证3.0999这种情况在保留一位小数的时候能是对的,这一位可以这么做没什么问题
var money = parseFloat(money).toFixed(3);
try{
var int_part = money.split(".")[0], // 小数点前的整数
point_num = money.split(".")[1], // 取小数点后面的小数
precision_num = point_num[3-precision];
if(precision_num>4){//五入的情况
if(precision==1){
point_num = parseInt(point_num)+10+"";
if(point_num.length>3){//说明往整数位进1
int_part = parseInt(int_part)+1+"";
point_num = point_num[1]+point_num[2];
}else{
point_num = point_num[0]+point_num[1];
}
result = parseFloat(int_part+"."+point_num);
}else if(precision==2){
point_num = parseInt(point_num)+100+"";
if(point_num.length>3){//说明往整数位进1
int_part = parseInt(int_part)+1+"";
point_num = point_num[1];
}else{
point_num = point_num[0];
}
result = parseFloat(int_part+"."+point_num);
}else if(precision==3){
int_part = parseInt(int_part)+1+"";
point_num = 0;
}
result = parseFloat(int_part+"."+point_num);
}else{//四舍的情况
if(precision==1){
point_num = point_num[0]+point_num[1];
}else if(precision==2){
point_num = point_num[0];
}else if(precision==3){
point_num = 0;
}
result = parseFloat(int_part+"."+point_num);
}
}
catch(e){
return parseFloat(money).toFixed(2);//如果过程中有出错就tofixed代替为解决
}
return result;
}
真心用了toFixed()后坑死我了。
把toFixed() 转换成数字类型的方法
但是这些后期也会有问题。
推荐写法 Math.round(x) 函数返回一个数字四舍五入后最接近的整数。
Math.round( x * 100) / 100
但是发现这个只能是两位小数。所以原生的Math.round()方法也不能满足我们的需求,要知道的是 Math.round()是JavaScript数学库中的函数,用于将给定数字浮点数舍入到最接近的整数值。
它的工作方式是,如果小数部分小于0.5,则四舍五入(最接近的整数小于它),如果小数部分大于0.5,则四舍五入(最近的整数大于它)。
Syntax:
句法:
Math.round(value);
所以我们一般会去封装一下,,各个公司都会针对数据四舍五入,时间进行封装。下边是我的封装
react项目
这里的rounds 接收两个参数,一个是计算的方式val,一个是保留的小数位
import { Fraction, BigNumber, MathArray, Matrix, add, subtract, multiply, divide, bignumber, round } from 'mathjs';
import { compose, curry, fromPairs, keys } from 'ramda';
// 数学计算工具类 - 处理高精度计算或者数字转化,提供数学算法。
type MType =
| number
| string
| Fraction
| BigNumber
| MathArray
| Matrix
| boolean
| Fraction
| null;
// 高精度计算 - 内部统一返回BigDecimal,再统一转成number类型。
const basicHpOperations = ((maths) =>
fromPairs(
keys(maths).map((operation) => [
operation,
curry(compose(parseFloat, (bn) => bn.toString(), maths[operation]))
])
))({
// 高精度加法
add: (sbj: MType, acc: MType) => {
return add(bignumber(sbj), bignumber(acc));
},
// 高精度减法
sub: (sbj: MType, acc: MType) => {
return subtract(bignumber(sbj), bignumber(acc));
},
// 高精度乘法
mul: (sbj: MType, acc: MType) => {
return multiply(bignumber(sbj), bignumber(acc));
},
// 高精度除法
div: (sbj: MType, acc: MType) => {
return divide(bignumber(sbj), bignumber(acc));
}
});
// 将内部计算解构成4种计算 - 注意 为了防止精度丢失,全部为string类型返回
const { add: hpAdd, sub, mul, div } = basicHpOperations;
// 包裹一层,给个初值
const wrapperAdd = (sbj = 0, acc = 0) => hpAdd(sbj, acc);
const wrapperSub = (sbj = 0, acc = 0) => sub(sbj, acc);
const wrapperMul = (sbj = 0, acc = 0) => mul(sbj, acc);
const wrapperDiv = (sbj = 0, acc = 0) => div(sbj, acc);
// 四舍五入
const rounds = (val: number, ex: number) => round(val, ex);
export default {
add: wrapperAdd,
sub: wrapperSub,
mul: wrapperMul,
div: wrapperDiv,
rounds
};
那么项目中就可以完成了rounds的用法了
const exclTaxCurAmt = maths.rounds(mul(exclTaxAmt, exchangeRate), toFixedAmt); // 不含税金额本位币
或者下图的样式写法,其实是一样的
这样就完成了。即可以计算,也可以正常保留小数位。