Requirement:
Convert input integer or decimal to an array and convert array of integers which may include a decimal to a number.
Re
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;}
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.