今天遇到一个坑,是JS中 toFixed() 方法的坑,一直以为这个方法是“四舍五入”, 今天才发现不完全是。
在IE 11中测试结果和正常的“四舍五入”没有什么区别:
Number(3.315).toFixed(2) "3.32" Number(3.325).toFixed(2) "3.33" Number(3.335).toFixed(2) "3.34" Number(3.345).toFixed(2) "3.35" Number(3.355).toFixed(2) "3.36" Number(3.365).toFixed(2) "3.37" Number(3.375).toFixed(2) "3.38" Number(3.385).toFixed(2) "3.39" Number(3.395).toFixed(2) "3.40"
但是在 Chrome 和 Firefox 下的结果却让我意外,它不完全符合“四舍五入”的规则,
Number(3.315).toFixed(2) "3.31" Number(3.325).toFixed(2) "3.33" Number(3.335).toFixed(2) "3.33" Number(3.345).toFixed(2) "3.35" Number(3.355).toFixed(2) "3.35" Number(3.365).toFixed(2) "3.37" Number(3.375).toFixed(2) "3.38" Number(3.385).toFixed(2) "3.38" Number(3.395).toFixed(2) "3.40"
一脸懵逼啊,百度得到一个说法:
toFixed它是一个四舍六入五成双的诡异的方法(也叫银行家算法),"四舍六入五成双"含义:对于位数很多的近似数,当有效位数确定后,其后面多余的数字应该舍去,只保留有效数字最末一位,这种修约(舍入)规则是“四舍六入五成双”,也即“4舍6入5凑偶”这里“四”是指≤4 时舍去,"六"是指≥6时进上,"五"指的是根据5后面的数字来定,当5后有数时,舍5入1;当5后无有效数字时,需要分两种情况来讲:①5前为奇数,舍5入1;②5前为偶数,舍5不进。(0是偶数)
没办法了,踩了这个坑只能自己来填了,自己实现四舍五入:
var Float = { toFixed: function (num, precision) { var times = Math.pow(10, precision == null ? 0 : precision); return (function (num) {return num.toFixed(precision)})(Math.floor(num * times + 0.5) / times); } }
对于数据的运算,计算机会把数值转为二进制进行计算,而在转换过程中可能出现精度问题,例如 0.012 + 0.001 = 0.013,但是JS运算结果却与我们期望的 0.013 有些出入:
0.012 + 0.001 0.013000000000000001为了处理这类问题,通常是将小数计算转为整数计算,看代码吧:
var Float = { /** * 四舍五入 * @param num * @param precision * @returns {string} */ toFixed: function (num, precision) { var times = Math.pow(10, precision == null ? 0 : precision); return (function (num) {return num.toFixed(precision)})(Math.floor(num * times + 0.5) / times); }, /** * 加法 * @returns {number} */ add: function () { var maxTimes = 1; var integers = []; var i, item, sum; for (i = 0; i < arguments.length; i++) { item = Float._toInteger(arguments[i]); integers.push(item); maxTimes = Math.max(maxTimes, item.times); } sum = 0; for (i = 0; i < integers.length; i++) { sum += integers[i].value * maxTimes / integers[i].times; } return sum / maxTimes; }, /** * 减法 * @param n1 * @param n2 * @returns {number} */ subtract: function (n1, n2) { var i1 = Float._toInteger(n1); var i2 = Float._toInteger(n2); if (i1.times > i2.times) { return (i1.value - i2.value * i1.times / i2.times) / i1.times; } else { return (i1.value * i2.times / i1.times - i2.value) / i2.times; } }, /** * 乘法 * @returns {number} */ multiply: function () { var values = 1; var times = 1; var item; for (var i = 0; i < arguments.length; i++) { item = Float._toInteger(arguments[i]); values *= item.value; times *= item.times; } return values / times; }, /** * 除法 * @param n1 * @param n2 */ divide: function (n1, n2) { var i1 = Float._toInteger(n1); var i2 = Float._toInteger(n2); if (i1.times == i2.times) { return i1.value / i2.value; } else if (i1.times > i2.times) { return (i1.value / i2.value) / (i1.times / i2.times); } else { return (i1.value / i2.value) * (i2.times / i1.times); } }, _toInteger: function (num) { var str = num + ""; var index = str.indexOf("."); return { value: parseInt(str.replace(".", "")), times: Math.pow(10, index < 0 ? 0 : str.substring(index + 1).length) }; } } // Test console.log("Float.toFixed(3.335, 2): ", Float.toFixed(3.335, 2)); console.log("Float.add(0.011, 0.001, 0.001): ", Float.add(0.011, 0.001, 0.001)); console.log("Float.subtract(0.014, 0.001): ", Float.subtract(0.014, 0.001)); console.log("Float.multiply(0.13, 0.1): ", Float.multiply(0.13, 0.1)); console.log("Float.divide(0.0013, 0.1): ", Float.divide(0.0013, 0.1));
Over!