AlexZ33 / lessions

自己练习的各种demo和课程
12 stars 2 forks source link

数值计算 #71

Open AlexZ33 opened 4 years ago

AlexZ33 commented 4 years ago

开发过程中免不了有浮点运算,JavaScript浮点运算的精度问题会带来一些困扰

JavaScript 只有一种数字类型 ( Number )

JavaScript采用 IEEE 754 标准双精度浮点(64),64位中 1位浮点数中符号,11存储指数,52位存储浮点数的有效数字

有时候小数在二进制中表示是无限的,所以从53位开始就会舍入(舍入规则是0舍1入),这样就造成了“浮点精度问题”(由于舍入规则有时大点,有时小点)

JavaScript加减乘除运算

var a = 0.1; var b = 0.2; console.log('0.1 以二进制表示:', a.toString(2)); console.log('0.2 以二进制表示:', b.toString(2)); console.log('直接加法运算 0.1 + 0.2 =', a + b); 示例: 0.1 + 0.2

预期结果:0.3

实际结果:0.30000000000000004

减法

var a = 1.0; var b = 0.7; console.log('1.0 以二进制表示:', a.toString(2)); console.log('0.7 以二进制表示:', b.toString(2)); console.log('直接减法运算 1.0 - 0.7 =', a - b); 示例: 1.0 - 0.7

预期结果:0.3

实际结果:0.30000000000000004

乘法

var a = 1.01; var b = 1.003; console.log('1.01 以二进制表示:', a.toString(2)); console.log('1.003 以二进制表示:', b.toString(2)); console.log('直接乘法运算 1.01 1.003 =', a b); 示例: 1.01 * 1.003

预期结果:1.01303

实际结果:1.0130299999999999 除法 var a = 0.029; var b = 10; console.log('0.029 以二进制表示:', a.toString(2)); console.log('10 以二进制表示:', b.toString(2)); console.log('直接除法运算 0.029 / 10 =', a / b); 示例: 0.029 / 10

预期结果:0.0029

实际结果:0.0029000000000000002

**说明:以上加、减、乘、除示例分别演示了JavaScript运算结果(当然实际结果并不是我们想要的),为什么会出现这样的结果,前言中已经说明^^!**_


/*eslint no-extend-native: ["off", { "exceptions": ["Object"] }]*/
/*eslint no-empty: "off"*/
export function numberMul(arg1, arg2) {
  return accMul(arg1, arg2);
}

/**
 * 对val的小数位数截断,不进行四舍五入
 * 注:toFixed()方法使用定点表示法来格式化一个数值,这就是个二进制浮点数转换定点数时精度丢失的问题,你看到的进位,实际上是浮点数在二进制-十进制转换中的一种必然损耗,不是简单的四舍五入或银行家舍入法,例如(0.045).toFixed(2)
 * @param {*} val
 * @param {*} precision
 */
export function numberFixedCut(val, precision) {
  var content = parseFloat(val);
  if (!content) {
    content = 0.00;
  }
  var arg_precision = 0;// 默认保留至整数
  if (precision) {
    arg_precision = parseInt(precision);
  }
  // ----开始处理----
  if (arg_precision === 0) {
    // 保留整数
    if (content.toString().indexOf('.') < 0) {
      return content
    }
    return Number(content.toString().split('.')[0]);
  }

  // ----保留小数----
  if (content.toString().indexOf('.') < 0) {
    return Number(content.toFixed(arg_precision))
  }

  var arr = content.toString().split('.')
  if (arr[1].length === arg_precision) {
    // 精度恰好一致
    return content
  } else if (arr[1].length < arg_precision) {
    // 精度不足
    return Number(content.toFixed(arg_precision))
  } else {
    // 精度超出,截断
    return Number(arr[0] + '.' + arr[1].substring(0, arg_precision))
  }
}
// 给Number类型增加一个add方法,调用起来更加方便。
Number.prototype.toFixedCut = function(precision) {
  return numberFixedCut(this, precision);
};

/**
 * 四舍五入
 * @param {*} val
 * @param {*} precision 小数位数
 */
function round(val, precision) {
  var content = parseFloat(val);
  if (!content) {
    content = 0.00;
  }
  var arg_precision = 0;// 默认保留至整数
  if (precision) {
    arg_precision = parseInt(precision);
  }
  var tmp = Math.pow(10, arg_precision)
  return Math.round(content * tmp) / tmp
}
// 给Number类型增加一个round方法,调用起来更加方便。
Number.prototype.round = function(precision) {
  return round(this, precision);
};

function transToDecimal(value) {
  value = value.toString()
  if (value.indexOf('.') < 0) {
    value += '.0';
  }
  return value
}
/**
 ** 加法函数,用来得到精确的加法结果
 ** 说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。
 ** 调用:accAdd(arg1,arg2)
 ** 返回值:arg1加上arg2的精确结果
 **/
function accAdd(arg1, arg2) {
  arg1 = transToDecimal(arg1);
  arg2 = transToDecimal(arg2);
  var r1, r2, m, c;
  try {
    r1 = arg1.toString().split('.')[1].length;
  } catch (e) {
    r1 = 0;
  }
  try {
    r2 = arg2.toString().split('.')[1].length;
  } catch (e) {
    r2 = 0;
  }
  c = Math.abs(r1 - r2);
  m = Math.pow(10, Math.max(r1, r2));
  if (c > 0) {
    var cm = Math.pow(10, c);
    if (r1 > r2) {
      arg1 = Number(arg1.toString().replace('.', ''));
      arg2 = Number(arg2.toString().replace('.', '')) * cm;
    } else {
      arg1 = Number(arg1.toString().replace('.', '')) * cm;
      arg2 = Number(arg2.toString().replace('.', ''));
    }
  } else {
    arg1 = Number(arg1.toString().replace('.', ''));
    arg2 = Number(arg2.toString().replace('.', ''));
  }
  return (arg1 + arg2) / m;
}

// 给Number类型增加一个add方法,调用起来更加方便。
Number.prototype.add = function(arg) {
  return accAdd(arg, this);
};
/**
 ** 减法函数,用来得到精确的减法结果
 ** 说明:javascript的减法结果会有误差,在两个浮点数相减的时候会比较明显。这个函数返回较为精确的减法结果。
 ** 调用:accSub(arg1,arg2)
 ** 返回值:arg1加上arg2的精确结果
 **/
function accSub(arg1, arg2) {
  arg1 = transToDecimal(arg1);
  arg2 = transToDecimal(arg2);
  var r1, r2, m, n;
  try {
    r1 = arg1.toString().split('.')[1].length;
  } catch (e) {
    r1 = 0;
  }
  try {
    r2 = arg2.toString().split('.')[1].length;
  } catch (e) {
    r2 = 0;
  }
  m = Math.pow(10, Math.max(r1, r2)); // last modify by deeka //动态控制精度长度
  n = (r1 >= r2) ? r1 : r2;
  return ((arg1 * m - arg2 * m) / m).toFixed(n);
}

// 给Number类型增加一个mul方法,调用起来更加方便。
Number.prototype.sub = function(arg) {
  return accSub(this, arg);
};
/**
 ** 乘法函数,用来得到精确的乘法结果
 ** 说明:javascript的乘法结果会有误差,在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果。
 ** 调用:accMul(arg1,arg2)
 ** 返回值:arg1乘以 arg2的精确结果
 **/
function accMul(arg1, arg2) {
  arg1 = transToDecimal(arg1);
  arg2 = transToDecimal(arg2);
  var m = 0; var s1 = arg1.toString(); var s2 = arg2.toString();
  try {
    m += s1.split('.')[1].length;
  } catch (e) {
  }
  try {
    m += s2.split('.')[1].length;
  } catch (e) {
  }
  return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m);
}

// 给Number类型增加一个mul方法,调用起来更加方便。
Number.prototype.mul = function(arg) {
  return accMul(arg, this);
};
/**
 ** 除法函数,用来得到精确的除法结果
 ** 说明:javascript的除法结果会有误差,在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。
 ** 调用:accDiv(arg1,arg2)
 ** 返回值:arg1除以arg2的精确结果
 **/
function accDiv(arg1, arg2) {
  arg1 = transToDecimal(arg1);
  arg2 = transToDecimal(arg2);
  var t1 = 0; var t2 = 0; var r1; var r2;
  try {
    t1 = arg1.toString().split('.')[1].length;
  } catch (e) {
  }
  try {
    t2 = arg2.toString().split('.')[1].length;
  } catch (e) {
  }
  r1 = Number(arg1.toString().replace('.', ''));
  r2 = Number(arg2.toString().replace('.', ''));
  return (r1 / r2) * Math.pow(10, t2 - t1);
}

// 给Number类型增加一个div方法,调用起来更加方便。
Number.prototype.div = function(arg) {
  return accDiv(this, arg);
};
AlexZ33 commented 4 years ago

// Math.cbrt方法用于计算一个数的立方根。
Math.cbrt(2)  // 1.2599210498948734

// ES5
parseInt('12.34') // 12
parseFloat('123.45#') // 123.45

// ES6

Number.parseInt('12.34') // 12
Number.parseFloat('123.45#') // 123.45

// Math.trunc方法用于去除一个数的小数部分,返回整数部分。
Math.trunc(4.6) // 4
Math.trunc(4.1) // 4

// Math.sign() 判断正数 +1 ,负数 -1 ,0 0, -0 -0 ,NaN
Math.sign(4) // +1
Math.sign(-2) // -1
Math.sign(-0) // -0
AlexZ33 commented 3 years ago

bigint

image

AlexZ33 commented 1 year ago

decimal.js

decimal.js加减乘除运算 An arbitrary-precision Decimal type for JavaScript GITHUB: https://github.com/MikeMcl/decimal.js

API: http://mikemcl.github.io/decimal.js/

NPM: https://www.npmjs.com/package/decimal.js

先安装decimal.js

npm install --save decimal.js 把上面的示例,用decimal.js运算一次,对比一下结果

var Decimal = require('decimal.js');

//加法
var a = 0.1;
var b = 0.2;
console.log('直接加法运算 a + b =', a + b);
console.log('Decimal.js加法运算 a + b =',  new Decimal(a).add(new Decimal(b)).toNumber());

//减法
var a = 1.0;
var b = 0.7
console.log('直接减法运算 a - b =', a - b);
console.log('Decimal.js减法运算 a - b =',  new Decimal(a).sub(new Decimal(b)).toNumber());

//乘法
var a = 1.01;
var b = 1.003;
console.log('直接乘法运算 a * b =', a * b);
console.log('Decimal.js乘法运算 a * b =',  new Decimal(a).mul(new Decimal(b)).toNumber());

//除法
var a = 0.029;
var b = 10;
console.log('直接除法运算 a / b =', a / b);
console.log('Decimal.js除法运算 a / b =',  new Decimal(a).div(new Decimal(b)).toNumber());