业务中常见的问题 原因和原理 js 所有的数字都遵循 IEE 754 标准,使用 64 位固定长度表示,这就是双精度浮点数的由来 这样做的目的是为了归一化处理数字,节省存储空间 64 位分为三部分 符号位(S):第 1 位符号位,0 代表正数,1 代表负数 指数为(E):中间 11 位指数位,代表次方数 位数位(M):最后 52 位是位数,超出的部分自动进 1 舍 0 转换公式 辅助工具: 十进制…

  • 业务中常见的问题
  1. 0.1+0.2 = 0.30000000000000004
  2. 1.005.toFixed(2)=1.00

原因和原理

  1. js 所有的数字都遵循 IEE 754 标准,使用 64 位固定长度表示,这就是双精度浮点数的由来
  2. 这样做的目的是为了归一化处理数字,节省存储空间
  3. 64 位分为三部分
    • 符号位(S):第 1 位符号位,0 代表正数,1 代表负数
    • 指数为(E):中间 11 位指数位,代表次方数
    • 位数位(M):最后 52 位是位数,超出的部分自动进 1 舍 0

floatpoint1

  1. 转换公式

floatpoint2

  1. 辅助工具:十进制转二进制科学记数法计算器IEEE754 双精度 64 位计算器
  • 0.1+0.2 = 0.30000000000000004 的原因和原理
二进制和科学计数法计算器可以用来辅助计算,IEEE754 双精度 64 位计算器可以查看S、E、M的值
-----------------------------------------------
以6.5为例
1、6.5的二进制是110.1
2、110.1的科学计数法是1.101 x 10^2
------------------------------------------------
根据公式推导
符号位:S=0
指数位:E-1023=2,E=1025,1025转二进制10000000001
尾数位:1.101 去掉个位,M=101
------------------------------------------------
0.1的二进制:0.000110011001100110
0.2的二进制:0.001100110011001100
0.1在计算机中的最准确的表示:1.00000000000000005551115123126E-1
0.2在计算机中的最准确的表示:2.00000000000000011102230246252E-1
------------------------------------------------------------
js中获取到准确值
0.1.toPrecision(64) = 0.1000000000000000055511151231257827021181583404541015625000000000
0.2.toPrecision(64) = 0.2000000000000000111022302462515654042363166809082031250000000000
----------------------------------------------------------------------------------------
计算机中的计算步骤
0.1的二进制+0.2的二进制后,再转换成十进制
最终结果就是0.30000000000000004
----------------------------------------------------------------------------------------
由最终结果可以看出,js实际上保留的精度是16位,也就是说
let x = 0.1
x的保留16位,0.1.toPrecision(16),那么就是0.1000000000000000
x如果保留20位,0.1.toPrecision(20),就是0.10000000000000000555
这就是为什么看到的x=0.1不是0.10000000000000000555,而是0.1000000000000000(去掉末尾的0就是0.1)
  • 1.005.toFixed(2)=1.00的原因和原理
1.005的实际值
1.005.toPrecision(64) = 1.004999999999999893418589635984972119331359863281250000000000000
所以1.004999999999999893418589635984972119331359863281250000000000000.toFixed(2) = 1.00

解决方法

// 来源:https://github.com/nefe/number-precision
'use strict';
/**
 * @desc 解决浮动运算问题,避免小数点后产生多位数和计算精度损失。
 * 问题示例:2.3 + 2.4 = 4.699999999999999,1.0 - 0.9 = 0.09999999999999998
 */
/**
 * 把错误的数据转正
 * strip(0.09999999999999998)=0.1
 */
function strip(num, precision) {
  if (precision === void 0) {
    precision = 12;
  }
  return +parseFloat(num.toPrecision(precision));
}
/**
 * Return digits length of a number
 * @param {*number} num Input number
 */
function digitLength(num) {
  // Get digit length of e
  var eSplit = num.toString().split(/[eE]/);
  var len = (eSplit[0].split('.')[1] || '').length - +(eSplit[1] || 0);
  return len > 0 ? len : 0;
}
/**
 * 把小数转成整数,支持科学计数法。如果是小数则放大成整数
 * @param {*number} num 输入数
 */
function float2Fixed(num) {
  if (num.toString().indexOf('e') === -1) {
    return Number(num.toString().replace('.', ''));
  }
  var dLen = digitLength(num);
  return dLen > 0 ? strip(num * Math.pow(10, dLen)) : num;
}
/**
 * 检测数字是否越界,如果越界给出提示
 * @param {*number} num 输入数
 */
function checkBoundary(num) {
  if (_boundaryCheckingState) {
    if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) {
      console.warn(num + ' is beyond boundary when transfer to integer, the results may not be accurate');
    }
  }
}
/**
 * 精确乘法
 */
function times(num1, num2) {
  var others = [];
  for (var _i = 2; _i < arguments.length; _i++) {
    others[_i - 2] = arguments[_i];
  }
  if (others.length > 0) {
    return times.apply(void 0, [times(num1, num2), others[0]].concat(others.slice(1)));
  }
  var num1Changed = float2Fixed(num1);
  var num2Changed = float2Fixed(num2);
  var baseNum = digitLength(num1) + digitLength(num2);
  var leftValue = num1Changed * num2Changed;
  checkBoundary(leftValue);
  return leftValue / Math.pow(10, baseNum);
}
/**
 * 精确加法
 */
function plus(num1, num2) {
  var others = [];
  for (var _i = 2; _i < arguments.length; _i++) {
    others[_i - 2] = arguments[_i];
  }
  if (others.length > 0) {
    return plus.apply(void 0, [plus(num1, num2), others[0]].concat(others.slice(1)));
  }
  var baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)));
  return (times(num1, baseNum) + times(num2, baseNum)) / baseNum;
}
/**
 * 精确减法
 */
function minus(num1, num2) {
  var others = [];
  for (var _i = 2; _i < arguments.length; _i++) {
    others[_i - 2] = arguments[_i];
  }
  if (others.length > 0) {
    return minus.apply(void 0, [minus(num1, num2), others[0]].concat(others.slice(1)));
  }
  var baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)));
  return (times(num1, baseNum) - times(num2, baseNum)) / baseNum;
}
/**
 * 精确除法
 */
function divide(num1, num2) {
  var others = [];
  for (var _i = 2; _i < arguments.length; _i++) {
    others[_i - 2] = arguments[_i];
  }
  if (others.length > 0) {
    return divide.apply(void 0, [divide(num1, num2), others[0]].concat(others.slice(1)));
  }
  var num1Changed = float2Fixed(num1);
  var num2Changed = float2Fixed(num2);
  checkBoundary(num1Changed);
  checkBoundary(num2Changed);
  return times(num1Changed / num2Changed, Math.pow(10, digitLength(num2) - digitLength(num1)));
}
/**
 * 四舍五入
 */
function round(num, ratio) {
  var base = Math.pow(10, ratio);
  return divide(Math.round(times(num, base)), base);
}
var _boundaryCheckingState = true;
/**
 * 是否进行边界检查,默认开启
 * @param flag 标记开关,true 为开启,false 为关闭,默认为 true
 */
function enableBoundaryChecking(flag) {
  if (flag === void 0) {
    flag = true;
  }
  _boundaryCheckingState = flag;
}
// 0.1 + 0.2 = 0.30000000000000004
plus(0.1, 0.2); // 0.3
// 1.0 - 0.9 = 0.09999999999999998
minus(1.0, 0.9); // 0.1
// 3*0.3 = 0.8999999999999999
times(3, 0.3); // 0.9
// 1.21/1.1 = 1.0999999999999999
divide(1.21, 1.1); //1.1
// 0.105.toFixed(2) = 0.10
round(0.105, 2); // 0.11

参考

暂无评论