/** * Created by jimiz on 2017/3/28. * 经验证:10000次四舍五入,用num.toFixed为15毫秒,用roundTo为47毫秒,速度低一些,但可以接受 * 另:经手工验证,用num.toString(2)将十进制浮点数转换为二进制浮点数时,最后一位有错误的情况出现,例子(10.0311) * * zhangyin 2018-02-28 * 采用重复一次四舍五入解决浮点精度误差后,10000次roundTo的时间为94毫秒。 * * zhangyin 2018-04-28 * 因为js中的浮点数本身就有误差,经过运算后误差被放大,用加尾数的方式也不能消除。所以必须采用另一种思路。 * 考虑误差会被放大,因此放弃一位有效位数来消除误差,二进制有效位数50位,十进制有效位数15位。 * 原重复四舍五入的方法有缺陷,放弃。 * 10000次roundTo时间恢复到47毫秒 */ let scMathUtil = { innerRoundTo: function(num, digit){ let lFactor = Math.pow(10, digit); let fOffSet = 0.5; let sign = ''; // 处理符号 if (num < 0){ sign = '-'; num = Math.abs(num); } // 计算 let result = Math.floor((num / lFactor) + fOffSet).toString(); let iLength = result.length; // 因为数值被转为整数计算,当目标位数在小数点后,且数值小于0,需要补齐前面的位数 if (iLength < -digit){ result = this.zeroString(-digit) + result; } // 当目标位数在小数点前,需要补齐后面的位数 else if ((digit > 0) && (iLength < digit)){ result = result + this.zeroString(digit); } iLength = result.length; // 获得小数点前的数字 let r1 = result.substring(0, iLength + digit); // 获得小数点后的数字 let r2 = result.substring(iLength + digit, iLength); // 拼出完整结果 return Number(sign + r1 + '.' + r2); }, // 原来直接用num.toString(2),如果小数段最后位数是0,会被舍掉,导致进位计算bug // 改为自己计算二进制,固定为53位。 // 经验证速度没有差别 // 另:经手工验证,用num.toString(2)将十进制浮点数转换为二进制浮点数时,最后一位有错误的情况出现,例子(10.0311) floatToBin: function(num) { let sign = ''; let dNum = num; // 符号位 if (num < 0) { sign = '-'; dNum = -num; }; // 解析整数段 let iNum = Math.floor(dNum); let iFactor; let sResult1 = ''; // 计算二进制整数段 while (iNum > 0){ iFactor = iNum % 2; iNum = Math.floor(iNum / 2); sResult1 = iFactor + sResult1; } // 判断是否有整数段 let bIntZero = sResult1 === ''; if (bIntZero){ sResult1 = '0'; } // 解析小数段 let fNum = dNum - Math.floor(dNum); let sResult2 = ''; if (fNum > 0){ // 双精度浮点数,尾数总长52位,因为第一位总是1,存储时已经被隐藏,所以有效位数为53位 // 由于js未对浮点数做优化,所以在有运算的情况下,误差会被放大,因此放弃一位有效位数来消除误差,二进制有效位数50位,十进制有效位数15位 const floatLength = 50; let iLength; // js的bug,浮点数直接取小数可能不能获得精确值,只有转成字符串,截取字符串中的小数段 let sNum = dNum.toString(10); let iDot = sNum.indexOf('.'); sNum = '0' + sNum.substring(iDot, sNum.length); fNum = Number(sNum); // 有整数段,则小数位数为全部位数-整数位数 if (!bIntZero){ iLength = floatLength - sResult1.length; } else{ iLength = floatLength; } // 计算二进制小数段 while (iLength > 0){ fNum = fNum * 2; iFactor = Math.floor(fNum); fNum = fNum % 1; sResult2 = sResult2 + iFactor; if (iFactor > 0){ bIntZero = false; } if (bIntZero && (iFactor === 0)){ continue; } iLength--; } } return sign + sResult1 + '.' + sResult2; }, binToFloat: function(bin) { let result = 0; let iLength = bin.length; let sign = ''; if (iLength > 0 && bin[0]==='-'){ sign = '-'; bin = bin.substring(1, iLength); } iLength = bin.length; let iDot = bin.indexOf('.'); if (iDot >= 0) { for (let i = 0; i < iLength; i++) { let num = Number(bin[i]); let idx = iDot - i; if (idx === 0) { continue }; if (idx > 0) { idx -= 1 }; let r = Math.pow(2, idx); result += num * r; } } else { result = parseInt(bin, 2); }; return sign + result; }, zeroString: function(length){ let result = ''; for (let i = 0; i < length; i++){ result = result + '0'; }; return result; }, incMantissa: function(bin){ let result = bin; let iDot = bin.indexOf('.'); if (iDot < 0){return result}; let iLength = bin.length; iLength = bin.length; for (let i = iLength - 1; i > iDot; i--){ let num = Number(bin[i]); if (num === 0){ num = 1; let bin1 = bin.substring(0, i); let bin2 = this.zeroString(iLength - (i + 1));//bin.substring(i + 1, iLength); result = bin1 + num.toString() + bin2; break; } }; return result; }, roundTo: function(num, digit){ let me = this; return me.innerRoundTo(me.binToFloat(me.incMantissa(me.floatToBin(num))), digit); }, isNumber : function (obj) { return obj === +obj; }, roundForObj:function(obj,decimal){ let me = this; let value; if(me.isNumber(obj)){ value = me.roundTo(obj,-decimal) }else { value = me.roundTo(Number(obj),-decimal); } return value }, roundToString:function(obj,decimal){ let me = this; let value; if(me.isNumber(obj)){ value = me.roundTo(obj,-decimal) }else { value = me.roundTo(Number(obj),-decimal); } return value.toFixed(decimal); }, isNumOrFormula:function (text) { let value = Number(text); if(value ==0 ) return value; if (!value) { try { let exp = new Expression(''); exp.Expression(text); value = Number(exp.Evaluate()); } catch (error) { value = null; } } if(text==null||text==""){ value = null; } return value; }, isDef:function (v) { return v !== undefined && v !== null; }, //获取ID引用 getFIDArr: function (exp) { let fidRex = /@[\d,a-z,A-Z,-]{36}/g; let fidArr = exp.match(fidRex); return this.isDef(fidArr) ? fidArr : []; } }; Number.prototype.toDecimal = function (ADigit) { //return parseFloat(this.toFixed(ADigit)); digit = ((ADigit!=null||ADigit!=undefined) && typeof(ADigit) === 'number' && Number.isInteger(ADigit) && ADigit >= 0) ? -ADigit : -2; // var s = scMathUtil.roundTo(this, digit); // console.log('Number: ' + this + ' Digit: ' + digit + ' Result: ' + s); // return parseFloat(s); return scMathUtil.roundTo(this, digit); };