math.js是如何处理小数问题的

1,883 阅读3分钟

相关的参考文档

Math.js是一个为javascript 和 node扩展math功能的库;

目录

math.js与小数

math.js 处理小数是使用bignumber 这个数据类型,但是其实质是用了decimal.js来处理的,所以我们如果要查看math.js处理小数的运算,可以直接看decimal.js

math.js中bignumber进行小数据的加减乘除运算的实质就是,使用人的思维进行运算,而非 转换二进制-> 运算 -> 返回十进制 这种计算机的方式

BigNumber

前面我们写到,BigNumber 这个类型其实是decimal.js,我们看代码或者看文档可以看到,其实bignumber是Decimal.clone()这个闭包返回的构造方法; 实例化后的对象是这样的:

let m = math.bignumber(1.234)
let m = math.bignumber(1.234)
undefined674584140@qq.com
m
{
    constructor: ƒ a(e),   //Decimal.clone()返回的类, 
    d: (2) [1, 2340000],   // m的数值信息
    e: 0,                  // 参考IEEE 754中exponse偏移量,以个位为基准
    s: 1,                  // 参考IEEE 754中的sign位 1 为正,-1为负(注意IEEE 754中sign 只有0 = 正数, 1 = 负数,因为Math.pow(-1,n),n=0时结果是1,n=1时结果是-1 );
}
decimal实例的字段说明
decimalInstance.constructor

可以理解为就是Decimal.close()返回的构造函数,方便后面的计算中随时实例他一个decimal实例出来;

decimalInstance.d

其中字段d是decimal的核心部分,理解了d就差不多可以理解decimal.js是如何处理小数问题的了。 d是一个最大四个元素的二维数组,里面的元素最多有7个元素,记录了数字从第一个非0数字开始的最多21位数字。 举个例子

math.bignumber(.1).d
[1000000]
math.bignumber(1.1).d
(2[1, 1000000]
math.bignumber(1.12345678).d
(3[1, 1234567, 8000000]
math.bignumber(987654321.12345678).d
(3[98, 7654321, 1234568]

math.bignumber(87654321.12345678).d  
(4[8, 7654321, 1234567, 8000000]
decimalInstance.e

e是属于偏移量,是我们对于d的补充,因为我们的d是从第一位非0的数字开始的,所以在e则负责提供d处理的位数;

decimalInstance.s

s属于标志位,1代表正数,-1代表负数;(需要注意与IEEE 754sign位的差异,因为IEEE 754是二进制数,所以它需要用01代表正负,所以IEEE 754中0是正数,1是负数)

BigNumber 转换成 number

其实质是Decimal 对象的valueOf 和 toString方法,不过看了源码,不管哪个,返回的结果都是string而非numbr

number 转换成 BigNumber

其实质是实例化一个decimal.js 对象:

  • Decimal 构造函数是通过decimal.js 的clone 这个闭包方法取得的;
  • math.bignumber方法其实就是调用了new Decimal(val);

new Decimal(val)的核心过程:

/**
 * 把数据进行处理,并返回结果Decimal 实例
 * @param {Decimal} x 初始化了的实例,已经计算好s了
 * @param {String} str 入参
 * @return {Decimal} 结果实例
 **/
function parseDecimal(x, str) {
    var e, i, len;

    // 找出小数点的位置,如果有,则清除小数点
    if ((e = str.indexOf('.')) > -1) str = str.replace('.', '');

    // 如果没有小数点,则理解为小数点在数字最后一位
    if (e < 0) {

      // Integer.
      e = str.length;
    }

    // Determine leading zeros.
    // 计算str第一个非0数字前面的0的个数,累计入i中;
    for (i = 0; str.charCodeAt(i) === 48; i++);

    // 清除str后面的0,记录str.length
    for (len = str.length; str.charCodeAt(len - 1) === 48; --len);
    str = str.slice(i, len);

    // 开始了...
    // 这里 i 第一个非0数字前面的0的个数,
    // e 是 小数点的位置,此两参数是用作计算decimal.e位移量的
    // len 
    if (str) {
      // 是为了计算出真实有效的数字的长度
      len -= i;
      // 计算出decimal.e 偏移量
      x.e = e = e - i - 1;
      // 重置decimal.d 数组 
      x.d = [];

      // 开始转换

      // e is the base 10 exponent. 
      // i is where to slice str to get the first word of the digits array.
      
      i = (e + 1) % LOG_BASE;
      // 如果是0.0X的情况,
      if (e < 0) i += LOG_BASE;
      // 如果有多个分组的情况,即从第一个非0数字起,总共有超过7位数字的情况
      if (i < len) {
        // 先取第一组
        if (i) x.d.push(+str.slice(0, i));
        // 遍历中间部分
        for (len -= LOG_BASE; i < len;) x.d.push(+str.slice(i, i += LOG_BASE));
        // 为最后一组准备
        str = str.slice(i);
        i = LOG_BASE - str.length;
      } else {
        // else 里面的是只有一组数据的情况
        i -= len;
      }
      // 遍历最后一组
      for (; i--;) str += '0';
      x.d.push(+str);

      if (external) {

        // Overflow?
        if (x.e > x.constructor.maxE) {

          // Infinity.
          x.d = null;
          x.e = NaN;

        // Underflow?
        } else if (x.e < x.constructor.minE) {

          // Zero.
          x.e = 0;
          x.d = [0];
          // x.constructor.underflow = true;
        } // else x.constructor.underflow = false;
      }
    } else {

      // Zero.
      x.e = 0;
      x.d = [0];
    }

    return x;
  }