五舍六入的问题
JavaScript中 Number.prototype.toFixed 方法,可以将数字保留指定位数的小数,最后一位小数通过其后面一位“四舍五入”得到。这里之所以要将“四舍五入”打上引号,是因为在 Chrome/Firefox 上,这个方法根据小数位数的不同(或整数部分是否为0),表现的行为可能是四舍五入,也可能是“五舍六入”。是的,你没听错,“五舍六入”,看下下面这组 Chrome 上的例子:
describe('toFixed() test', function () {
it('Number.toFixed()', function () {
expect(1.35.toFixed(1)).toBe("1.4"); // true
expect(1.335.toFixed(2)).toBe("1.34"); // false
expect(1.3335.toFixed(3)).toBe("1.334"); // false
expect(1.33335.toFixed(4)).toBe("1.3334"); // true
expect(1.333335.toFixed(5)).toBe("1.33334"); // false
expect(1.3333335.toFixed(6)).toBe("1.333334"); // false
});
});
上面代码中的有部分最后一个位是 5,却被舍弃掉了,即“五舍六入”。这会导致页面中,有些显示小数的地方出现最后一位与预期不符的情况。这种情况在 IE6-IE10 上没出现,但是在 Chrome44/Firefox41 上出现了。
修复方式
这篇文章提供了两种修复方法,一起看一看:
第一种修复方式:判断最后一位小数为 5 的,改成 6, 再调用 toFixed。例如 1.335(调用 toFixed(2) 结果为 1.33)先修改为 1.336,在调用 toFixed(2) ,得到的结果是 1.34,符合预期。
function toFixed(num, fractionDigits) {
var str = num + ''
var len = str.length
var last = str.substr(len-1, len)
if (last == '5') {
last = '6'
str = str.substr(0, len-1) + last
return Number(str).toFixed(fractionDigits)
} else {
return num.toFixed(fractionDigits)
}
}
第二种修复方式:将数字先进行放大指定倍数,使最后一位小数显示成个位数,然后加上一个 0.5 后,使最后一位进位(如果最后一位大于等于 5),再调用 parsInt() 去尾并缩小相同倍数。例如 1.335 放大 100 倍后是 133.5,加上 0.5 后是 134.0,调用 parseInt() 得到 134,再缩小 100 倍得到 1.34,结果符合预期。
function toFixed(num, fractionDigits) {
var times = Math.pow(10, fractionDigits)
var des = num * times + 0.5
des = parseInt(des, 10) / times
return String(des)
}
上面两种方式都可以修复“五舍六入”的问题,但是实现方法和计算复杂度都稍大,下面我提供一种简单的方法。
第三种修复方式:在调用 toFixed() 方法前,先给数字补一个极小值,然后再调用 toFixed() 方法。例如 1.335 加上一个极小值(0.00000001)后是 1.33500001,然后调用 toFixed(2)得到 1.34,结果符合预期。这个极小值只要不影响最后结果,可以根据要保留的小数位数决定取多少,但也不能太小了(例如 1.3350000001.toFixed(2) 结果是 1.34,而 1.33500000000000001.toFixed(2) 结果是 1.33)。
function toFixed(num, fractionDigits) {
return (num + 0.00000001).toFiexed(fractionDigits);
}
使用第三种方式修复后,再次运行测试代码,都可以得到正确的结果:
describe('toFixed() test', function () {
it('myToFixed()', function () {
var oldToFixed = Number.prototype.toFixed;
// 这里重写原生方法是为了方便比较,不推荐这样做
Number.prototype.toFixed = function (n) {
return oldToFixed.call(this + 0.00000001, n);
}
expect(1.35.toFixed(1)).toBe("1.4");
expect(1.335.toFixed(2)).toBe("1.34");
expect(1.3335.toFixed(3)).toBe("1.334");
expect(1.33335.toFixed(4)).toBe("1.3334");
expect(1.333335.toFixed(5)).toBe("1.33334");
expect(1.3333335.toFixed(6)).toBe("1.333334");
Number.prototype.toFixed = oldToFixed;
});
});