JS中toFixed()方法四舍五入的精度问题详解

目录
  • 踩的坑
  • 填坑方法
  • 什么样的坑?
  • 总结

踩的坑

最近工作中,在计算一个商品的折扣价格,有时候总是出现价格会有一分钱的差异,涉及钱的问题都是比较敏感的,经过排查,最后发现竟然是 JS 原生的 toFixed 方法的问题。

好家伙,这都啥规律啊。。。(⊙o⊙)

填坑方法

先不着急去探究其中的问题,既然发现了问题,那就先把 Bug 修复了先,原生方法用不了,就自己写一个呗,还不是分分钟的事情,哈哈哈!

/**
 * 保留小数点几位数, 自动补零, 四舍五入
 * @param num: 数值
 * @param digit: 小数点后位数
 * @returns string
 */
function myFixed(num, digit) {
  if(Object.is(parseFloat(num), NaN)) {
    return console.log(`传入的值:${num}不是一个数字`);
  }
  num = parseFloat(num);
  return (Math.round((num + Number.EPSILON) * Math.pow(10, digit)) / Math.pow(10, digit)).toFixed(digit);
}

什么样的坑?

好了,既然 Bug 解决完了,下面我们就来探索一下 toFixed 其中的奥秘。

呃...首先,Em...百度一下吧,面向百度编程工程师,果然一查大把结果,看来是个经典问题了。

经过一番了解得知,原来是 toFixed 方法采用的四舍五入,并不是我们理解的字面上的四舍五入。而 toFixed 方法它是采用一种诡异的方法 "四舍六入五取偶" ,也叫银行家算法,这是什么个意思呢?

完整说法:"四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一"。

大概意思是:当舍去位的数值 ≤4 时舍去,当它 ≥6 时加上,可当它 =5 时,则根据 5 后面的数字来定;当 5 后有非零数字时,舍 5 入 1;当 5 后无有效数字时,需要再分两种情况:5 前为偶数,舍 5 不进;5 前为奇数,舍 5 入 1 。

根据这个规则,我在浏览器上又跑了一些测试,但却还是感觉不对。

// 五前是偶数,没有舍去?
console.log(1.00000065.toFixed(7)); // 1.0000007 错误
console.log(1.000000065.toFixed(8)); // 1.00000007 错误
// 五前是奇数,没有进一?
console.log(1.00000015.toFixed(7)); // 1.0000001 错误
console.log(1.000000015.toFixed(8)); // 1.00000001 错误

这到底为啥?真是让人摸不着头脑。。。(︶︿︶)

再经过一番探索,总算是有点收获了,下面就得来看看 ECMAScript规范对该方法的定义了,有时候回归规范才是最靠谱的方式。

上图是关于整个 toFixed 方法的定义,不过是翻译后的版本,会有出入但差别不大,也可以点击上面的链接查看原文,我们主要关注图中红框部分,通过公式来计算舍去位数值。

我们下面两个来举个栗子,测试一下。

console.log(1.0000005.toFixed(6)); // 1.000001 正确
console.log(1.00000005.toFixed(7)); // 1.0000000 错误

首先,根据红框的条件,x<10^21,1.0000005 与 1.00000005 都是小于 10^21 的,所以我们直接可以使用公式 n / 10^ - x 来玩耍。

我们先用 x=1.0000005 代入公式来看看情况:

// 假设n1
var n1 = 1000000;
var x = 1.0000005;
var f = 6;
console.log((n1 / Math.pow(10, f) - x)); // -5.00000000069889e-7

// 假设n2
var n2 = 1000001;
var x = 1.0000005;
var f = 6;
console.log((n2 / Math.pow(10, f) - x)); // 4.999999998478444e-7

由结果可知,当 n1=1000001 时,得到的结果取最接近 0 的值,故:

console.log(1.0000005.toFixed(6)); // 1.000001 正确

再来试试当 x=1.00000005 代入公式:

// 假设n1
var n1 = 10000000;
var x = 1.00000005;
var f = 7;
console.log((n1 / Math.pow(10,f) - x)); // -4.9999999918171056e-8

// 假设n2
var n2 = 10000001;
var x = 1.00000005;
var f = 7;
console.log((n2 / Math.pow(10,f) - x)); // 5.000000014021566e-8

由结果可知,当 n2=10000001 时,得到的结果取最接近 0 的值,故:

console.log(1.00000005.toFixed(7)); // 1.0000000 错误

哎...写到这里才发现给自己挖了一个大坑,干嘛要搞那么多零,自己都数晕圈了。。。

总的来说,上面例子就是教你如何通过规范定义的公式计算出结果而已,如果你看得懂规范,那么直接去代入也是没有问题的。

总结

到此这篇关于JS中toFixed()方法四舍五入精度问题的文章就介绍到这了,更多相关JS toFixed()四舍五入精度问题内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JavaScript 正则表达式详解

    目录 1. 正则表达式创建 2. 使用模式 2.1 使用简单模式 2.2 使用特殊字符 3. 应用 3.1 切分字符串 3.2 分组 3.3 贪婪匹配 3.4 正则表达式标志 3.5 test() 方法 4. 常用正则(参考) 总结 1. 正则表达式创建 JavaScript 有两种方式创建正则表达式: 第一种:直接通过/正则表达式/写出来 第二种:通过new RegExp('正则表达式')创建一个RegExp对象 const re1 = /ABC\-001/; const re2 = new

  • js正则表达式简单校验方法

    对于字符串的一些操作,可以通过正则表达式来实现.一般的搜索操作想必大家已经学会,今天就来说说它的校验功能,这样可以帮助判断字符串类型或者是其它的组成,比如密码.中文.字符串的组成等.下面就js正则表达式的校验带来内容分享,同时要考虑在js中支持的类型. 1.常见js正则校验 (1)校验密码强度 密码的强度必须是包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间. ^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$ (2)校验中文 字符串仅能是中文. ^

  • js用正则表达式筛选年月日的实例方法

    当我们想要对日期进行筛选时,可以选择使用正则表达式的检索功能.这里涉及到正则表达式关于匹配的使用,本篇会就组匹配的基础知识带来分析和代码展示.再学会了正则表达式的匹配方法后,就筛选日期的方法进行过程介绍,下面一起来看看正则表达式的相关内容吧. 1.组匹配 正则表达式的括号表示分组匹配,括号中的模式可以用来匹配分组的内容. /fred+/.test('fredd') // true /(fred)+/.test('fredfred') // true 上面代码中,第一个模式没有括号,结果+只表示重

  • JS使用tofixed与round处理数据四舍五入的区别

    1 .tofixed方法 toFixed() 方法可把 Number 四舍五入为指定小数位数的数字.例如将数据Num保留2位小数,则表示为:toFixed(Num):但是其四舍五入的规则与数学中的规则不同,使用的是银行家舍入规则,银行家舍入:所谓银行家舍入法,其实质是一种四舍六入五取偶(又称四舍六入五留双)法.具体规则如下: 简单来说就是:四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一. 显然这种规则不符合我们平常在数据中处理的方式.为了解决这样的问题,可以自定义

  • jmeter实现接口关联的两种方式(正则表达式提取器和json提取器)

    目录 一.前言 二.使用正则表达式提取器实现接口关联 三.使用json提取器实现接口关联 json提取器的使用步骤 四.扩展:返回复杂json数据的提取 一.前言 在开展接口测试或者是接口面试的过程中,我们会发现很多接口需要依赖前面的接口,需要我们动态从前面的接口返回中提取数据,也就是我们通常说的关联. 关联通俗来讲就是把上一次请求的返回内容中的部分截取出来保存为参数,用来传递给下一个请求使用. 二.使用正则表达式提取器实现接口关联 正则表达式提取器,见名知意就是使用正则表达式的方法把我们需要提

  • JS处理数据四舍五入(tofixed与round的区别详解)

    1 .tofixed方法 toFixed() 方法可把 Number 四舍五入为指定小数位数的数字.例如将数据Num保留2位小数,则表示为:toFixed(Num):但是其四舍五入的规则与数学中的规则不同,使用的是银行家舍入规则,银行家舍入:所谓银行家舍入法,其实质是一种四舍六入五取偶(又称四舍六入五留双)法.具体规则如下: 简单来说就是:四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一. 显然这种规则不符合我们平常在数据中处理的方式.为了解决这样的问题,可以自定义

  • 盘点javascript 正则表达式中 中括号的【坑】

    在javascript中使用正则时需要注意中括号里边的一个坑,那就是中括号内的元字符问题.自己踩到坑了,网上搜了一下还有不少人踩了这个坑,所以大概说一下. 中括号在正则中称为字符组(Character class),有的书翻译为字符类,还有的翻译成字符集.我觉得字符组更好点,毕竟class在计算机属于中代表面向对象里的"类".顾名思义,字符组为一组字符,它表示在一个位置里可能出现的多种字符.注意这里强调只匹配一个位置哦.(此段摘自-http://www.cnblogs.com/snan

  • javascript中toFixed()四舍五入使用方法详解

    最近做的项目涉及到金额的计算,有一种方式就是进行四舍五入的规则进行小数点后面的尾数处理,以前一直以为toFixed方法就是四舍五入的,知道一个用户反馈了金额计算的bug我才如梦初醒(亏了一毛钱),才仔细深究了下toFixed这个方法,唉,还是我不够严谨啊,前车之鉴,大家勿走我的老路! toFixed还不同的浏览器实现,在IE10及以上里面是正常的四舍五入,但是别的浏览器里面就不一样了,它不是正常的四舍五入(等下重点说),比如: var a = 1.335; console.log(a.toFix

  • 在nest.js中通过正则表达式正确设置验证的方法

    下面看下nest.js正则表达式设置验证的方法,代码如下所示: import { IsNotEmpty, Length, Matches, Max, Min } from "class-validator"; const phoneReg = /^1(3\d|4[5-9]|5[0-35-9]|6[567]|7[0-8]|8\d|9[0-35-9])\d{8}$/ 补充:下面看下js正则表达式验证大全 /判断输入内容是否为空     function IsNull(){        

  • jJavaScript中toFixed()和正则表达式的坑

    目录 toFixed精度问题 导致原因 解决办法 正则表达式全局匹配的坑 toFixed精度问题 toFixed方法可以把Number四舍五入为指定小数位数的数字.可是大家看下下面这张图,发现了什么? 0.985四舍五入之后变成了0.98!! 这就是toFixed方法的坑. 导致原因 那这到底是怎么回事呢?本质其实是因为js小数的精度问题. 在计算机中计算,是将数字转成二进制,进行计算之后再转化为十进制. 比如将0.985转化为二进制是0.1111110000101000(超出精度,结果保留了1

随机推荐