# 数字计算

关于浮点数的存储和计算,会丢失精度这个事情就不说了, 这块需要去了解计算机是怎么存储浮点数,以及为什么会产生这个精度问题的,我们就讨论一下我们js里面常遇到的问题,以及一些解决办法即可。

比如我们经常做加法遇到, 非常常见的一个问题

0.1 + 0.2 => 0.30000000000000004

我们现在就是要解决如何正确的获取到0.3这个结果即可。 实用为主。具体的办法还真得去好好看看,这个文章就提供2个三方库解决这个问题。

# 1、number-precision

一个是比较轻量级的number-precision

const NP = require('number-precision');
NP.strip(0.09999999999999998); // = 0.1
NP.plus(0.1, 0.2); // = 0.3, not 0.30000000000000004
NP.plus(2.3, 2.4); // = 4.7, not 4.699999999999999
NP.minus(1.0, 0.9); // = 0.1, not 0.09999999999999998
NP.times(3, 0.3); // = 0.9, not 0.8999999999999999
NP.times(0.362, 100); // = 36.2, not 36.199999999999996
NP.divide(1.21, 1.1); // = 1.1, not 1.0999999999999999
NP.round(0.105, 2); // 0.11

保证一般简单的加减乘除没问题。

# 2、BigNumber

接下来学习下BigNumber (opens new window),他的API特别多, 我们可以先学会最基础的加减乘除

# 加减乘除幂

const x = BigNumber(0.1);
const y = BigNumber(0.2);
// 加法
console.log(x.plus(y).toString());
console.log(x.plus(y).toNumber());

const z = BigNumber(0.3);
// 减法
console.log(z.minus(0.1).toNumber()); // 0.2
// 乘法
console.log(z.times(y).toNumber()); // 0.06
// 除法
console.log(z.div(y).toNumber()); // 1.5
// 指数
const x1 = new BigNumber(0.7)
console.log(x1.pow(2).toNumber()); // 0.49

# 四舍五入取整操作

rm value desc desc-cn
ROUND_UP 0 Rounds away from zero 远离0取值, 也就是正数向上取整 负数向下取整
ROUND_DOWN 1 Rounds towards zero 趋向0取值, 也就是正数向下取整 负数向上取整
ROUND_CEIL 2 Rounds towards Infinity 向上取值
ROUND_FLOOR 3 Rounds towards -Infinity 向下取值
ROUND_HALF_UP 4 Rounds towards nearest neighbour. If equidistant, rounds away from zero 四舍五入取值, 如果等距就远离0
ROUND_HALF_DOWN 5 Rounds towards nearest neighbour. If equidistant, rounds towards zero 四舍五入取值, 如果等距就趋向0
ROUND_HALF_EVEN 6 Rounds towards nearest neighbour. If equidistant, rounds towards even neighbour 四舍五入取值, 如果等距就取偶数值
ROUND_HALF_CEIL 7 Rounds towards nearest neighbour. If equidistant, rounds towards Infinity 四舍五入取值,向下取值
ROUND_HALF_FLOOR 8 Rounds towards nearest neighbour. If equidistant, rounds towards -Infinity 四舍五入取值,向上取值
// 四舍五入取整
const BigNumber = require('bignumber.js');
BigNumber.config({ ROUNDING_MODE: 1 });
// ROUNDING_MODE

const x = new BigNumber(123.456);

// BigNumber.ROUND_CEIL 向上取整
console.log(x.integerValue(BigNumber.ROUND_CEIL).toNumber()); // 124
// BigNumber.ROUND_FLOOR 向下取整
console.log(x.integerValue(BigNumber.ROUND_FLOOR).toNumber()); // 123

// BigNumber.ROUND_UP 远离0取整, 也就是正数向上取整 负数向下取整
const a1 = BigNumber(10.4);
const a2 = BigNumber(-10.4);
console.log(a1.integerValue(BigNumber.ROUND_UP).toNumber()); // 11
console.log(a2.integerValue(BigNumber.ROUND_UP).toNumber()); // -11

// BigNumber.ROUND_DOWN 趋向0取整, 也就是正数向下取整 负数向上取整
const b1 = BigNumber(10.4);
const b2 = BigNumber(-10.4);
console.log(b1.integerValue(BigNumber.ROUND_DOWN).toNumber()); // 10
console.log(b2.integerValue(BigNumber.ROUND_DOWN).toNumber()); // -10

// BigNumber.ROUND_HALF_UP 就近取整, 如果等距就远离0
const c1 = BigNumber(10.3);
const c2 = BigNumber(-10.6);
console.log(c1.integerValue(BigNumber.ROUND_HALF_UP).toNumber()); // 10
console.log(c2.integerValue(BigNumber.ROUND_HALF_UP).toNumber()); // -11

// BigNumber.ROUND_HALF_DOWN 就近取整, 如果等距就趋于0
const d1 = BigNumber(10.3);
const d2 = BigNumber(-10.5);
console.log(d1.integerValue(BigNumber.ROUND_HALF_DOWN).toNumber()); // 10
console.log(d2.integerValue(BigNumber.ROUND_HALF_DOWN).toNumber()); // -10

// BigNumber.ROUND_HALF_EVEN 就近取整, 如果等距就取偶数整
console.log(BigNumber(10.5).integerValue(BigNumber.ROUND_HALF_EVEN).toNumber()); // 10
console.log(BigNumber(-11.5).integerValue(BigNumber.ROUND_HALF_EVEN).toNumber()); // -12

// BigNumber.ROUND_HALF_CEIL 就近取整, 如果等距向上取整
// BigNumber.ROUND_HALF_DOWN 就近取整, 如果等距向下取整
# 其他实用操作
// 绝对值
const v = new BigNumber(-0.8);
console.log(v.abs().toNumber()); // 0.8

// 比较
const v1 = new BigNumber(1);
const v2 = new BigNumber(2);
console.log(v1.comparedTo(v2)); // comparedTo 可以理解为 >=, v1 >= v2, 大于返回1 小于返回-1 等于返回0

// 判断相等
const v3 = BigNumber(0.3);
const v4 = BigNumber(0.3);
console.log(v3.isEqualTo(0.3)); // true
console.log(v3.isEqualTo(v3)); // true
console.log(v3.isEqualTo(v4)); // true

// 判断大于
console.log(BigNumber(0.3).gt(BigNumber(0.2))); // true
console.log(BigNumber(0.2).gt(BigNumber(0.2))); // false

// 大于等于
console.log(BigNumber(0.3).gte(BigNumber(0.2))); // true
console.log(BigNumber(0.2).gte(BigNumber(0.2))); // true

// 小于 小于等于  lt  lte

// 开方运算
console.log(Math.sqrt(0.04));
console.log(BigNumber(0.04).sqrt().toNumber()); // 0.2
# 数字显示API
// toExponential 输出指数形式
console.log(BigNumber('1000003131230000').toExponential()); // 1.00000313123e+15

// toString 在一定范围内输出数字形式,超出返回指数形式
console.log(BigNumber('1000003131230000').toString()); //1000003131230000
console.log(BigNumber('100000313123000000000000000').toString()); //1.00000313123e+26

// toFixed 总是输出数字形式
console.log(BigNumber('100000313123000000000000000').toFixed()); // 100000313123000000000000000
// 保留小数位数, HALF
console.log(BigNumber('123.456').toFixed(2, BigNumber.ROUND_FLOOR)); // 123.45
console.log(BigNumber('123.451').toFixed(2, BigNumber.ROUND_CEIL)); // 123.46
console.log(BigNumber('123.451').toFixed(2, BigNumber.ROUND_HALF_DOWN)); // 123.45
console.log(BigNumber('123.455').toFixed(2, BigNumber.ROUND_HALF_UP)); // 123.46

// toFormat 格式化
console.log(BigNumber('100000313123000000000000000').toFormat(3)); // 100,000,313,123,000,000,000,000,000.000

// toNumber
console.log(BigNumber(0.1).plus(0.2).toNumber()); // 数字 0.3, 不是字符串

个人觉得 BigNumber 在处理一般的业务时, 足够使用了。毕竟实用为主么

# 3、轻量化简易加减乘除

一个比较简单的加减乘除,能满足一般的要求,参数最好传入字符串,应该都是字符串的一些。 但是肯定还有一些问题的,比如参数传入0.00000001,toString之后就变成了'1e-8'的科学计数形式,

function mul(a, b) {
  let c = 0,
    d = a.toString(),
    e = b.toString();
  try {
    c += d.split('.')[1].length;
  } catch (f) {}
  try {
    c += e.split('.')[1].length;
  } catch (f) {}
  return (Number(d.replace('.', '')) * Number(e.replace('.', ''))) / Math.pow(10, c);
}

function add(a, b) {
  let c, d, e;
  try {
    c = a.toString().split('.')[1].length;
  } catch (f) {
    c = 0;
  }
  try {
    d = b.toString().split('.')[1].length;
  } catch (f) {
    d = 0;
  }
  e = Math.pow(10, Math.max(c, d));
  return (mul(a, e) + mul(b, e)) / e;
}

function sub(a, b) {
  let c, d, e;
  try {
    c = a.toString().split('.')[1].length;
  } catch (f) {
    c = 0;
  }
  try {
    d = b.toString().split('.')[1].length;
  } catch (f) {
    d = 0;
  }
  e = Math.pow(10, Math.max(c, d));
  return (mul(a, e) - mul(b, e)) / e;
}

function divide(a, b) {
  var c,
    d,
    e = 0,
    f = 0;
  try {
    e = a.toString().split('.')[1].length;
  } catch (g) {
    e = 0;
  }
  try {
    f = b.toString().split('.')[1].length;
  } catch (g) {
    f = 0;
  }
  c = Number(a.toString().replace('.', ''));
  d = Number(b.toString().replace('.', ''));
  return mul(c / d, Math.pow(10, f - e));
}

// 四舍五入保留小数位数
function round(num, precision) {
  const base = Math.pow(10, precision);
  return (Math.round((num.toPrecision(17) * base).toFixed(1)) / base).toFixed(precision);
}

// 截位小数部分 intercept(0.1365, 2) => '0.13', 不进行4舍五入操作
function intercept(num, len) {
  let a, b;
  try {
    const c = num.toString().split('.');
    a = c[0] || '';
    b = c[1] || '';
  } catch (error) {
    a = '';
    b = '';
  }
  b = b.substring(0, len);
  return toNonExponential([a, b].filter(Boolean).join('.'));
}

/**
 * 把一个数字转换为非科学计数显示的字符串
 * 超出精度的还是有问题的
 * @param {string | number} num
 * @return {string}
 */
function toNonExponential(num) {
  if (typeof num === 'string') {
    num = num - 0;
  }
  var m = num.toExponential().match(/\d(?:\.(\d*))?e([+-]\d+)/);
  return num.toFixed(Math.max(0, (m[1] || '').length - m[2]));
}

/**
 * 列表求和
 *
 * @param {Array<number | string>} [args=[]]
 * @return {string}
 */
function sum(...args) {
  console.log(args);
  args = [...args].filter(num => {
    if (typeof num === 'string') {
      num = num.trim();
    }
    return Boolean(num);
  });
  let _sumVal = args.reduce((prev, cur) => {
    return add(prev, cur);
  }, 0);
  return toNonExponential(_sumVal);
}

// console.log(sum(...[1, 2, 3, 4, 0.1, 0.2, '', 0, false, ' ']));

export { mul, add, sub, divide, round, intercept, sum, toNonExponential };

上次更新: 4/1/2025, 9:33:10 AM