当前位置: 首页 > news >正文

toFixed() 踩坑----四舍六入 银行家算法

你绝对想不到 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); // 不含税金额本位币
或者下图的样式写法,其实是一样的
在这里插入图片描述
这样就完成了。即可以计算,也可以正常保留小数位。

切记切记,金额计算不要用toFixed()方法。

相关文章:

  • 基于.net开发chrome核心浏览器
  • juery 选择器 选择多个元素
  • Object.keys() 判断每一行的值是否相等
  • IBM公布Kitura 1.0和Bluemix Runtime for Swift 3
  • vsCode 文件保存自动格式化 设置
  • 程序员客栈携手野狗 体验国内领先的实时后端云协作
  • MyEclipse中点击Deploy MyEclipse J2EE Project to Server无响应解决方法
  • react 项目 计算列表金额 数据 汇总并实时变更
  • MongoDB学习(一)
  • Xcode8 Could not build Objective-C module 'FBSDKCoreKit'
  • 何为数据挖掘?
  • 【思路】-URL重写
  • 看完深入浅出的Javascript,简单写下
  • js封装加减乘除四则运算解决精度丢失的问题
  • 必填校验 验证问题
  • Java IO学习笔记一
  • java中的hashCode
  • Mybatis初体验
  • PhantomJS 安装
  • PHP 的 SAPI 是个什么东西
  • puppeteer stop redirect 的正确姿势及 net::ERR_FAILED 的解决
  • quasar-framework cnodejs社区
  • SpiderData 2019年2月16日 DApp数据排行榜
  • vue--为什么data属性必须是一个函数
  • webpack4 一点通
  • 如何设计一个微型分布式架构?
  • 算法-图和图算法
  • 想写好前端,先练好内功
  • 一个项目push到多个远程Git仓库
  • 白色的风信子
  • Mac 上flink的安装与启动
  • 测评:对于写作的人来说,Markdown是你最好的朋友 ...
  • #我与Java虚拟机的故事#连载10: 如何在阿里、腾讯、百度、及字节跳动等公司面试中脱颖而出...
  • (6)添加vue-cookie
  • (C++)八皇后问题
  • (LeetCode 49)Anagrams
  • (Redis使用系列) Springboot 使用redis的List数据结构实现简单的排队功能场景 九
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (更新)A股上市公司华证ESG评级得分稳健性校验ESG得分年均值中位数(2009-2023年.12)
  • (十三)Maven插件解析运行机制
  • (已更新)关于Visual Studio 2019安装时VS installer无法下载文件,进度条为0,显示网络有问题的解决办法
  • (转)大型网站架构演变和知识体系
  • (转载)hibernate缓存
  • (轉貼) 資訊相關科系畢業的學生,未來會是什麼樣子?(Misc)
  • *1 计算机基础和操作系统基础及几大协议
  • .NET 4.0网络开发入门之旅-- 我在“网” 中央(下)
  • .Net Redis的秒杀Dome和异步执行
  • .NET 使用 XPath 来读写 XML 文件
  • .Net中wcf服务生成及调用
  • .net中应用SQL缓存(实例使用)
  • .secret勒索病毒数据恢复|金蝶、用友、管家婆、OA、速达、ERP等软件数据库恢复
  • @Repository 注解
  • [20150707]外部表与rowid.txt
  • [ARM]ldr 和 adr 伪指令的区别
  • [BZOJ3223]文艺平衡树