Javascript中 toFixed 的‘奇葩坑’

时间:2022-03-20 14:46:26
以前一直以为toFixed就是四舍五入的方法,后来又有一段时间以为toFixed是五舍六入。今天终于写的时候,终于才知道toFixed是一个叫做四舍六入无成双的诡异的方法。。。

什么是四舍六入五成双:百度是这么说的:

对于位数很多的近似数,当有效位数确定后,其后面多余的数字应该舍去,只保留有效数字最末一位,这种修约(舍入)规则是“四舍六入五成双”,也即“4舍6入5凑偶”这里“四”是指≤4 时舍去,""是指≥6时进上,""指的是根据5后面的数字来定,当5后有数时,舍5入1;当5后无有效数字时,需要分两种情况来讲:①5前为奇数,舍5入1;②5前为偶数,舍5不进。(0是偶数)

toFixed使用的是银行家舍入规则。 银行家舍入:所谓银行家舍入法,其实质是一种四舍六入五取偶(又称四舍六入五留双)法。 简单来说就是: 四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一。 但是不论引入toFixed解决浮点数计算精度缺失的问题也好,它有没有使用银行家舍入法也罢,都是为了解决精度的问题,但是又离不开二进制浮点数的环境,但至少他帮助我们找到了问题所在,从而让我们有解决方法。
所以也就是说: ( 0.5251).toFixed( 2) => 0.53 然而 ( 0.525).toFixed( 2)=> 0.52 ,这并不是我们想用的四舍五入的方法。

通过重写toFixed的方法:
        Number.prototype.toFixed = function(length){
            var carry = 0; //存放进位标志
            var num, multiple; //num为原浮点数放大multiple倍后的数,multiple为10的length次方
            var str = this + ''; //将调用该方法的数字转为字符串
            var dot = str.indexOf("."); //找到小数点的位置
            if(str.slice(dot + length + 1, dot + length + 2) >= 5) carry = 1; /*找到要进行舍入的数的位置,手动判断是否大于等于5,满足条件进位标志置为1,这里原作者用的是str.substr(dot + length + 1, 1)*/
            multiple = Math.pow(10, length); //设置浮点数要扩大的倍数
            num = Math.floor(this * multiple) + carry; //去掉舍入位后的所有数,然后加上我们的手动进位数
            var result = num / multiple + ''; //将进位后的整数再缩小为原浮点数
            /*
            * 处理进位后无小数
            */
            dot = result.indexOf(".");
            if(dot === -1){
                result += '.';
                dot = result.indexOf(".");
            }
            /*
            * 处理多次进位
            */
            var len = result.length - (dot+1);
            if(len < length){
                for(var i = 0; i < length - len; i++){
                    result += 0;
                }
            }
            return result;
        }

再来个解决js计算浮点数精度问题(0.1+0.2或0.1*0.1这类问题)的方法:
                /*
		* f: 转换之前的小数
		* digit: 正常结果的小数位数
		*/
		var numA = 0.1; 
		var numB = 0.2;
		console.log(re = numA + numB)//0.30000000000000004
		Math.formatFloat = function(f, digit) { 
		    var m = Math.pow(10, digit); 
		    return parseInt(f * m, 10) / m; 
		} 

		console.log(Math.formatFloat(re, 1))//0.3