Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Requirement:

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

Restriction:

Do not use string methods or convert input or output to a string during the procedure (a self-imposed restriction followed throughout each version of the code composed).

Context and use cases

BigInt in available in some browsers, though not a BigDecimal. The conversion from integer or decimal to array and array to integer or decimal should be possible using the JavaScript programming language. The input and output should not need to be converted to a string during the procedure.

Ability to adjust nth digit of an integer or decimal by adjusting decimal or integer at nth index of array, to try to solve OEIS A217626 directly, for example

~~(128.625*9*1.074)//1243
~~(128.625*9*1.144)//1324

where the decimal portion can be manipulated by referencing the index of an array, then converting the array back to a number.

The current specification is WIP and could be considered challenging to describe relevant to the processing of the decimal portion of input, specifically where there are leading zeros.

Input <----------> Output

-123               [-1,-2,-3]
4.4                [4,0.4]
44.44              [4,4,0.4,4]
-0.01              [-0.01]
123                [1,2,3]
200                [2,0,0]
2.718281828459     [2,0.7,1,8,2,8,1,8,2,8,4,5,8,9]
321.7000000001     [3,2,1,0.7,0,0,0,0,0,0,0,0,1]
809.56             [8,0,9,0.5,6]
1.61803398874989   [1,0.6,1,8,0,3,3,9,8,8,7,4,9,8,9]
1.999              [1,0.9,9,9]
100.01             [1,0,0,0.01]
545454.45          [5,4,5,4,5,4,0.4,5]
-7                 [-7]
-83.782            [-8,-3,-0.7,-8,-2]
1.5                [1,0.5]
100.0001           [1,0,0,0.0001]

Essentially, am attempting to spread an integer or decimal to an array. The function which converts a number or integer to an array must be capable of being converted to a generator function, for the ability to achieve

[...Math.E] -> [2, 0.7, 1, 8, 2, 8, 1, 8, 2, 8, 4, 5, 9] -> 2.718281828459

by setting the function as value of Number.prototype[Symbol.iterator] to numberToArray.

The most recent version of the code (some of the concepts and original versions of the code were based on questions and answers at Get decimal portion of a number with JavaScript; Converting int value to String without using toString and parseInt method; Convert integer to array of digits), which has two bugs at resulting output of tests cases from arrayToNumber 100.05010000000497 should be 100.00015 and -83.082 should be -83.782.

function numberToArray(n) {

  if (Math.abs(n) == 0 || Math.abs(n) == -0) {
    return [n]
  }

  const r = [];

  let [
    a, int = Number.isInteger(a), d = g = [], e = i = 0
  ] = [ n || this.valueOf()];

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

  for (; ~~a; r.unshift(~~(a % 10)), a /= 10);

  if (!int) {
    for (; ~~d; g.unshift(~~(d % 10)), d /= 10);
    g[0] = g[0] * (1 * (10 ** -i))
    r.push(...g);
  }

  return r;

}

function arrayToNumber(a) {
  if ((Math.abs(a[0]) == 0 || Math.abs(a[0]) == -0) 
     && a.length == 1) return a[0];
  const [
    g, r = x => x.length == 1 
                ? x[0] 
                : x.length === 0 
                  ? x 
                  : x.reduce((a, b) => a + b)
    , b = a.find(x => g(x)), p = a.findIndex(x => g(x))
  ] = [x => !Number.isInteger(x)];

  let [i, j] = [b ? p : a.length, -1];

  return a.length === 1 
    ? a[0] 
    : b && p 
      ? r(a.slice(0, p).map(x => i ? x * (10 ** --i) : x)) 
        + (a[p] + (a[p + 1] !== undefined 
          ? r(a.slice(p + 1).map(x => x * (10 ** --j))) 
          : 0)) 
      : r(a.map(x => i ? x * (10 ** --i) : x))
}

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)]);

let numbers = arrays.map(n => arrayToNumber(n));

console.log({tests, arrays, numbers});

Questions:

  1. How to fix the listed bugs in the existing code?
  2. Within the restriction of not using string methods or converting input or output to a string during the procedure, can the code be improved or composed in a different manner altogether to meet the requirement?
  3. Can the current specification be improved as to clarity of terms used and to avoid confusion as to what the expected output is for decimals?
like image 215
guest271314 Avatar asked Jan 30 '19 03:01

guest271314


People also ask

How do you convert a string of numbers to an array of numbers?

You can convert a String to integer using the parseInt() method of the Integer class. To convert a string array to an integer array, convert each element of it to integer and populate the integer array with them.

Can we convert int to array in Java?

Get the set of integers. Create an object of Primitive int by ArrayUtils. toPrimitive() method of Apache Commons Lang's library. Convert this primitive int to array of integer by use of toArray() method.


1 Answers

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.

like image 136
Shidersz Avatar answered Sep 28 '22 06:09

Shidersz