scMathUtil.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. /**
  2. * Created by jimiz on 2017/3/28.
  3. * 经验证:10000次四舍五入,用num.toFixed为15毫秒,用roundTo为47毫秒,速度低一些,但可以接受
  4. * 另:经手工验证,用num.toString(2)将十进制浮点数转换为二进制浮点数时,最后一位有错误的情况出现,例子(10.0311)
  5. *
  6. * zhangyin 2018-02-28
  7. * 采用重复一次四舍五入解决浮点精度误差后,10000次roundTo的时间为94毫秒。
  8. *
  9. * zhangyin 2018-04-28
  10. * 因为js中的浮点数本身就有误差,经过运算后误差被放大,用加尾数的方式也不能消除。所以必须采用另一种思路。
  11. * 考虑误差会被放大,因此放弃一位有效位数来消除误差,二进制有效位数50位,十进制有效位数15位。
  12. * 原重复四舍五入的方法有缺陷,放弃。
  13. * 10000次roundTo时间恢复到47毫秒
  14. */
  15. let scMathUtil = {
  16. innerRoundTo: function(num, digit){
  17. let lFactor = Math.pow(10, digit);
  18. let fOffSet = 0.5;
  19. let sign = '';
  20. // 处理符号
  21. if (num < 0){
  22. sign = '-';
  23. num = Math.abs(num);
  24. }
  25. // 计算
  26. let result = Math.floor((num / lFactor) + fOffSet).toString();
  27. let iLength = result.length;
  28. // 因为数值被转为整数计算,当目标位数在小数点后,且数值小于0,需要补齐前面的位数
  29. if (iLength < -digit){
  30. result = this.zeroString(-digit) + result;
  31. }
  32. // 当目标位数在小数点前,需要补齐后面的位数
  33. else if ((digit > 0) && (iLength < digit)){
  34. result = result + this.zeroString(digit);
  35. }
  36. iLength = result.length;
  37. // 获得小数点前的数字
  38. let r1 = result.substring(0, iLength + digit);
  39. // 获得小数点后的数字
  40. let r2 = result.substring(iLength + digit, iLength);
  41. // 拼出完整结果
  42. return Number(sign + r1 + '.' + r2);
  43. },
  44. // 原来直接用num.toString(2),如果小数段最后位数是0,会被舍掉,导致进位计算bug
  45. // 改为自己计算二进制,固定为53位。
  46. // 经验证速度没有差别
  47. // 另:经手工验证,用num.toString(2)将十进制浮点数转换为二进制浮点数时,最后一位有错误的情况出现,例子(10.0311)
  48. floatToBin: function(num) {
  49. let sign = '';
  50. let dNum = num;
  51. // 符号位
  52. if (num < 0) {
  53. sign = '-';
  54. dNum = -num;
  55. };
  56. // 解析整数段
  57. let iNum = Math.floor(dNum);
  58. let iFactor;
  59. let sResult1 = '';
  60. // 计算二进制整数段
  61. while (iNum > 0){
  62. iFactor = iNum % 2;
  63. iNum = Math.floor(iNum / 2);
  64. sResult1 = iFactor + sResult1;
  65. }
  66. // 判断是否有整数段
  67. let bIntZero = sResult1 === '';
  68. if (bIntZero){
  69. sResult1 = '0';
  70. }
  71. // 解析小数段
  72. let fNum = dNum - Math.floor(dNum);
  73. let sResult2 = '';
  74. if (fNum > 0){
  75. // 双精度浮点数,尾数总长52位,因为第一位总是1,存储时已经被隐藏,所以有效位数为53位
  76. // 由于js未对浮点数做优化,所以在有运算的情况下,误差会被放大,因此放弃一位有效位数来消除误差,二进制有效位数50位,十进制有效位数15位
  77. const floatLength = 50;
  78. let iLength;
  79. // js的bug,浮点数直接取小数可能不能获得精确值,只有转成字符串,截取字符串中的小数段
  80. let sNum = dNum.toString(10);
  81. let iDot = sNum.indexOf('.');
  82. sNum = '0' + sNum.substring(iDot, sNum.length);
  83. fNum = Number(sNum);
  84. // 有整数段,则小数位数为全部位数-整数位数
  85. if (!bIntZero){
  86. iLength = floatLength - sResult1.length;
  87. }
  88. else{
  89. iLength = floatLength;
  90. }
  91. // 计算二进制小数段
  92. while (iLength > 0){
  93. fNum = fNum * 2;
  94. iFactor = Math.floor(fNum);
  95. fNum = fNum % 1;
  96. sResult2 = sResult2 + iFactor;
  97. if (iFactor > 0){
  98. bIntZero = false;
  99. }
  100. if (bIntZero && (iFactor === 0)){
  101. continue;
  102. }
  103. iLength--;
  104. }
  105. }
  106. return sign + sResult1 + '.' + sResult2;
  107. },
  108. binToFloat: function(bin) {
  109. let result = 0;
  110. let iLength = bin.length;
  111. let sign = '';
  112. if (iLength > 0 && bin[0]==='-'){
  113. sign = '-';
  114. bin = bin.substring(1, iLength);
  115. }
  116. iLength = bin.length;
  117. let iDot = bin.indexOf('.');
  118. if (iDot >= 0) {
  119. for (let i = 0; i < iLength; i++) {
  120. let num = Number(bin[i]);
  121. let idx = iDot - i;
  122. if (idx === 0) {
  123. continue
  124. };
  125. if (idx > 0) {
  126. idx -= 1
  127. };
  128. let r = Math.pow(2, idx);
  129. result += num * r;
  130. }
  131. }
  132. else {
  133. result = parseInt(bin, 2);
  134. };
  135. return sign + result;
  136. },
  137. zeroString: function(length){
  138. let result = '';
  139. for (let i = 0; i < length; i++){
  140. result = result + '0';
  141. };
  142. return result;
  143. },
  144. incMantissa: function(bin){
  145. let result = bin;
  146. let iDot = bin.indexOf('.');
  147. if (iDot < 0){return result};
  148. let iLength = bin.length;
  149. iLength = bin.length;
  150. for (let i = iLength - 1; i > iDot; i--){
  151. let num = Number(bin[i]);
  152. if (num === 0){
  153. num = 1;
  154. let bin1 = bin.substring(0, i);
  155. let bin2 = this.zeroString(iLength - (i + 1));//bin.substring(i + 1, iLength);
  156. result = bin1 + num.toString() + bin2;
  157. break;
  158. }
  159. };
  160. return result;
  161. },
  162. roundTo: function(num, digit){
  163. let me = this;
  164. return me.innerRoundTo(me.binToFloat(me.incMantissa(me.floatToBin(num))), digit);
  165. },
  166. isNumber : function (obj) {
  167. return obj === +obj;
  168. },
  169. roundForObj:function(obj,decimal){
  170. let me = this;
  171. let value;
  172. if(me.isNumber(obj)){
  173. value = me.roundTo(obj,-decimal)
  174. }else {
  175. value = me.roundTo(Number(obj),-decimal);
  176. }
  177. return value
  178. },
  179. roundToString:function(obj,decimal){
  180. let me = this;
  181. let value;
  182. if(me.isNumber(obj)){
  183. value = me.roundTo(obj,-decimal)
  184. }else {
  185. value = me.roundTo(Number(obj),-decimal);
  186. }
  187. return value.toFixed(decimal);
  188. },
  189. isNumOrFormula:function (text) {
  190. let value = Number(text);
  191. if(value ==0 ) return value;
  192. if (!value) {
  193. try {
  194. let exp = new Expression('');
  195. exp.Expression(text);
  196. value = Number(exp.Evaluate());
  197. } catch (error) {
  198. value = null;
  199. }
  200. }
  201. if(text==null||text==""){
  202. value = null;
  203. }
  204. return value;
  205. },
  206. isDef:function (v) {
  207. return v !== undefined && v !== null;
  208. },
  209. //获取ID引用
  210. getFIDArr: function (exp) {
  211. let fidRex = /@[\d,a-z,A-Z,-]{36}/g;
  212. let fidArr = exp.match(fidRex);
  213. return this.isDef(fidArr) ? fidArr : [];
  214. }
  215. };
  216. Number.prototype.toDecimal = function (ADigit) {
  217. //return parseFloat(this.toFixed(ADigit));
  218. digit = ((ADigit!=null||ADigit!=undefined) && typeof(ADigit) === 'number' && Number.isInteger(ADigit) && ADigit >= 0) ? -ADigit : -2;
  219. // var s = scMathUtil.roundTo(this, digit);
  220. // console.log('Number: ' + this + ' Digit: ' + digit + ' Result: ' + s);
  221. // return parseFloat(s);
  222. return scMathUtil.roundTo(this, digit);
  223. };