/**
 * 数学模块，主要针对浮点型数据，在运算时的精度问题：
 * 1| 两个小数相加 0.1 + 0.2 = 0.30000000000000004
 * 2| 两个小数相乘 2.12 * 3.14 = 6.6568000000000005;
 * 3| ...
 * 4| ...
 * 5| 数值长度大于13位时 显示科学计数法
 * @author lautin
 * @created 2019-11-19 17:15:04
 */

/**
 * 检测小数点精度
 * @param {number} num 传入的数值
 */
function countDecimals(num) {
    let precision;
    try {
        precision = num.toString().split(".")[1].length;
    } catch (e) {
        precision = 0;
    }
    return precision;
}

/**
 * js在以下情景会自动将数值转换为科学计数法：
 * 小数点前的数字个数大于等于22位；
 * 小数点前边是0，小数点后十分位（包含十分位）之后连续0的个数>=6时
 * @param {number} 科学计数法显示的数值 1.2e-8
 * @return {number} 返回实际的数值 1.00000002
 */
function scientific2No(val) { //-7.50375e-8

    // 正则匹配科学计数法的数字
    if (/\d+\.?\d*e[+-]*\d+/i.test(val)) {

        let zero = '0',
            parts = String(val).toLowerCase().split('e'), // ['-7.50375', '-8']
            e = parts.pop(), // 存储指数 -8
            l = Math.abs(e), // 0的个数  8
            sign = e / l,    // 判断正负 -
            // 将系数按照小数点拆分
            coeff_array = parts[0].split('.'); // [-7, 50375] 去除中间的.
        // 如果恰好为8位 那么第二个数默认为undefined  需要重置为'' 
        if (!coeff_array[1]) coeff_array[1] = '';
        if (sign === -1) { // 小数
            // debugger;
            // 正数或者负数
            if (coeff_array[0] < 0) {
                val = '-' + zero + '.' + zero.repeat(l - 1) + coeff_array[0].slice(1) + coeff_array[1];
            } else {
                val = zero + '.' + zero.repeat(l - 1) + coeff_array.join(''); //拼接字符串，如果是小数，拼接0和小数点
            }

        } else {

            let dec = coeff_array[1];

            // 如果是整数，将整数除第一位之外的非零数字计入位数，相应的减少0的个数
            if (dec) l = l - dec.length;

            // 拼接字符串，如果是整数，不需要拼接小数点
            val = coeff_array.join('') + new Array(l + 1).join(zero);

        }
    }


    try {
        return val.toString();
    } catch (e) {
        return '';
    }
}

/**
 * 截取小数点后n位
 * @param {number} val 截取的数值
 * @param {number} scale 保留的小数点位数
 */
function omitTo(val, scale) {

    // 转化科学计数法
    val = scientific2No(val);

    let ret;
    // 检测浮点数
    if (val.toString().indexOf(".") > -1) {
        // 提取实体集和精度值
        let [entity, precisionVal] = val.toString().split(".");

        if (precisionVal.length > scale) {
            // trunc() 方法会将数字的小数部分去掉，只保留整数部分。
            let tmp = scientific2No(Math.trunc(val * Math.pow(10, scale)));
            // 处理零值
            if (tmp == 0) ret = scientific2No('0.' + '0'.repeat(scale));
            else {
                // ret = tmp / ;
                ret = division(tmp, Math.pow(10, scale));
                try { // 小数

                    let [a, b] = ret.toString().split(".");
                    a = scientific2No(a),
                        b = scientific2No(b);

                    if (b.length < scale) {
                        ret = a + '.' + b.padEnd(scale, '0');
                    }
                } catch (e) { // 整数
                    ret = ret + '.' + '0'.padEnd(scale, '0');
                }
            }

        } else if (precisionVal.length == scale) { // 精度

            ret = val;

        } else {
            // 补全小数点
            ret = entity + '.' + precisionVal.padEnd(scale, '0');
        }

        // 检测整型值
    } else ret = val + '.' + '0'.repeat(scale);

    // 去除末尾可能产生的多余的. 
    if (ret.toString().endsWith('.')) ret = ret.slice(0, -1);

    return ret;
}

/**
 * 计算两个数的和
 * @param {number} num1
 * @param {number} num2
 */
function add(num1, num2, scale = null) {

    num1 = scientific2No(num1);
    num2 = scientific2No(num2);

    let amplification, // 放大率
        precision1 = countDecimals(num1), // 精度1
        precision2 = countDecimals(num2); // 精度2
    amplification = Math.pow(10, Math.max(precision1, precision2));

    // 先放大再相加，然后除以放大率
    let val = (num1 * amplification + num2 * amplification) / amplification;

    // 转化科学计数法
    let result = scientific2No(val);

    // 控制显示长度
    if (scale) result = omitTo(result, scale);

    return result;
}


/**
 * 计算两个数的差值
 * @param {number} num1
 * @param {number} num2
 */
function subtr(num1, num2, scale = null) {
    num1 = scientific2No(num1);
    num2 = scientific2No(num2);

    let amplification, // 放大率
        precision1 = countDecimals(num1), // 精度1
        precision2 = countDecimals(num2); // 精度2

    let precision = Math.max(precision1, precision2)
    amplification = Math.pow(10, precision);

    // 动态控制精度长度
    let val = ((num1 * amplification - num2 * amplification) / amplification).toFixed(precision);

    // 转化科学计数法
    let result = scientific2No(val);

    // 控制显示长度
    if (scale) result = omitTo(result, scale);

    return result;
}

/**
 * 计算两个数的乘积
 * @param {number} num1
 * @param {number} num2
 */
function multiple(num1, num2, scale = null) {
    num1 = scientific2No(num1);
    num2 = scientific2No(num2);

    let precision = 0;
    precision += countDecimals(num1);
    precision += countDecimals(num2);

    let val = Number(num1.toString().replace(".", "")) * Number(num2.toString().replace(".", "")) / Math.pow(10, precision);


    let result = scientific2No(val);

    if (scale) result = omitTo(result, scale);

    return result;
}


/**
 * 两个数相除
 * @param {number} num1
 * @param {number} num2
 * @param {optional} d
 */
function division(num1, num2, scale = null) {
    num1 = scientific2No(num1);
    num2 = scientific2No(num2);

    let precision1 = countDecimals(num1),
        precision2 = countDecimals(num2),
        m = precision1 > precision2 ? precision1 : precision2;

    // 两个整数相除 无需计算精度
    if (m <= 1) m = 1;
    let val = multiple(num1, m) / multiple(num2, m);
	
    let result = scientific2No(val);

    if (scale) result = omitTo(result, scale);

    return result;
}


Object.assign(Math, {
    countDecimals,
    add,
    subtr,
    multiple,
    division,
    scientific2No,
    omitTo
})

export default {
    countDecimals,
    add,
    subtr,
    multiple,
    division,
    scientific2No,
    omitTo
}