Number (integer or decimal) to array, array to number (integer or decimal) without using strings

前端 未结 1 969
青春惊慌失措
青春惊慌失措 2020-12-31 22:56

Requirement:

Convert input integer or decimal to an array and convert array of integers which may include a decimal to a number.

Re

相关标签:
1条回答
  • 2020-12-31 23:33

    Method numberToArray():

    I have been working some time on your implementation, and thinked to first analyze the numberToArray() method. To start, I have decided to create a method for analyze a decimal number and return statistics about it, basically, the information you where getting from this part of your code:

    if (!int) {
        let e = ~~a;
        d = a - e;
        do {
            if (d < 1) ++i;
            d *= 10;
        } while (!Number.isInteger(d));
    }
    

    The method I have made on is the next one (will be used inside numberToArray()) and basically gets the next information:

    1) Integer section (iSection) of the decimal number (as integer).

    2) Decimal section (dSection) of the decimal number (as integer).

    3) Number of digits after the dot (dDigits).

    4) Number of leading zeros after the dot (dZeros).

    function getDecimalStats(dec)
    {
        let dDigits = 0, test = dec, factor = 1, dZeros = 0;
    
        // Store the integer section of the decimal number.
    
        let iSection = ~~dec;
    
        // Get the numbers of digits and zeros after the comma.
        
        while (!Number.isInteger(test))
        {
            factor = Math.pow(10, ++dDigits);
            test = dec * factor;
            dZeros += Math.abs(test - (iSection * factor)) < 1 ? 1 : 0;
        }
    
        // Store the decimal section as integer.
    
        let dSection = test - (iSection * factor);
    
        // Return an object with all statistics.
    
        return {iSection, dSection, dZeros, dDigits};
    };
    
    console.log(getDecimalStats(10.001));
    console.log(getDecimalStats(-210.1));
    console.log(getDecimalStats(-0.00001));
    .as-console {background-color:black !important; color:lime;}
    .as-console-wrapper {max-height:100% !important; top:0;}

    Of course, if you dislike, you can put this same logic directly inside numberToArray() method. So, after making the previous function, I have done some reorganization on your code and added some commentaries to helps me understand what you where doing. Finally, and after adapted your code, I have found that the wrong mapping to the arrays was mostly because the arithmetic precision when operating with float number. After investigate some time about this problem, I found a solution that is based using a mathematical correction factor (it is commented on the code when it is applied). All in all, and until this time, I have come with the next solution to the numberToArray() method.

    function getDecimalStats(dec)
    {
        let dDigits = 0, test = dec, factor = 1, dZeros = 0;
    
        // Store the integer section of the decimal number.
    
        let iSection = ~~dec;
    
        // Get the numbers of digits and zeros after the comma.
        
        while (!Number.isInteger(test))
        {
            factor = Math.pow(10, ++dDigits);
            test = dec * factor;
            dZeros += Math.abs(test - (iSection * factor)) < 1 ? 1 : 0;
        }
    
        // Store the decimal section as integer.
    
        let dSection = test - (iSection * factor);
    
        // Return an object with all statistics.
    
        return {iSection, dSection, dZeros, dDigits};
    };
    
    function numberToArray(n)
    {
        let r = [];
    
        if (Math.abs(n) == 0)
            return [n];
    
        let [a, int = Number.isInteger(a), g = []] = [n || this.valueOf()];
    
        // Get the stats of the decimal number.
    
        let {dSection, dZeros} = getDecimalStats(a);
    
        // Push the integer part on the array.
    
        for (; ~~a; r.unshift(~~(a % 10)), a /= 10);
    
        // Push the decimal part on the array.
    
        if (!int)
        {
            // Push decimal digits on temporal array "g".
            for (; ~~dSection; g.unshift(~~(dSection % 10)), dSection /= 10);
    
            // Define the correction factor for the next operation.
            let cf = 10 ** (++dZeros);
    
            // Map g[0] to a decimal number and push elements on the array.
            g[0] = (g[0] * cf) * ((10 ** -dZeros) * cf) / (cf * cf);
            r.push(...g);
        }
    
        return r;
    }
    
    let tests = [
    0, 200, 100.00015, -123, 4.4, 44.44, -0.01, 123,
    2.718281828459, 321.7000000001, 809.56,
    1.61803398874989, 1.999, 100.01, 545454.45,
    -7, -83.782, 12, 1.50, 100.0001
    ];
    
    let arrays = tests.map(n => [...numberToArray(n)]);
    console.log({tests, arrays});
    .as-console {background-color:black !important; color:lime;}
    .as-console-wrapper {max-height:100% !important; top:0;}

    Method arrayToNumber():

    For this one I decided to go on my own (actually ignoring your current logic). The next approach will use the previously mentioned getDecimalStats() and mainly the Array::reduce():

    function getDecimalStats(dec)
    {
        let dDigits = 0, test = dec, factor = 1, dZeros = 0;
    
        // Store the integer section of the decimal number.
    
        let iSection = ~~dec;
    
        // Get the numbers of digits and zeros after the comma.
        
        while (!Number.isInteger(test))
        {
            factor = Math.pow(10, ++dDigits);
            test = dec * factor;
            dZeros += Math.abs(test - (iSection * factor)) < 1 ? 1 : 0;
        }
    
        // Store the decimal section as integer.
    
        let dSection = test - (iSection * factor);
    
        // Return an object with all statistics.
    
        return {iSection, dSection, dZeros, dDigits};
    };
    
    function arrayToNumber(a)
    {
        // Get the index of the first decimal number.
    
        let firstDecIdx = a.findIndex(
            x => Math.abs(x) > 0 && Math.abs(x) < 1
        );
    
        // Get stats about the previous decimal number.
    
        let {dZeros} = getDecimalStats(firstDecIdx >= 0 ? a[firstDecIdx] : 0);
    
        // Normalize firstDecIdx.
    
        firstDecIdx = firstDecIdx < 0 ? a.length : firstDecIdx;
    
        // Reduce the array to get the number.
        
        let number = a.reduce(
            ({num, dIdx, dPow}, n, i) =>
            {
                // Define the correction factor.
                let cf = 10 ** (dPow + i - dIdx);
    
                if (i < dIdx)
                   num += n * (10 ** (dIdx - i - 1));
                else if (i === dIdx)
                   num = ((num * cf) + (n * cf)) / cf;
                else
                   num = ((num * cf) + n) / cf;
    
                return {num, dIdx, dPow};
            },
            {num: 0, dIdx: firstDecIdx, dPow: ++dZeros}
        );
    
        return number.num;
    }
    
    let tests = [
        [0],
        [2, 0, 0],
        [1, 0, 0, 0.0001, 5],
        [-1, -2, -3],
        [4, 0.4],
        [4, 4, 0.4, 4],
        [-0.01],
        [1, 2, 3],
        [2, 0.7, 1, 8, 2, 8, 1, 8, 2, 8, 4, 5, 9],
        [3, 2, 1, 0.7, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [8, 0, 9, 0.5, 6],
        [1, 0.6, 1, 8, 0, 3, 3, 9, 8, 8, 7, 4, 9, 8, 9],
        [1, 0.9, 9, 9],
        [1, 0, 0, 0.01],
        [5, 4, 5, 4, 5, 4, 0.4, 5, 0],
        [-7],
        [-8,-3, -0.7, -8, -2],
        [1, 2],
        [1, 0.5],
        [1, 0, 0, 0.0001]
    ];
    
    let numbers = tests.map(n => arrayToNumber(n));
    console.log(numbers);
    .as-console {background-color:black !important; color:lime;}
    .as-console-wrapper {max-height:100% !important; top:0;}

    Finally, I hope you can value my efforts, and obviously there can be a lot of improvements to my solution (so, any recommendation is welcome). For example, there are currently none or few checks for safety.

    0 讨论(0)
提交回复
热议问题