将数字(整数或小数)转换为数组,将数组转换为数字(整数或小数),不使用字符串

6

需求:

将输入的整数或小数转换为数组,并将可能包含小数的整数数组转换为数字。

限制:

在过程中不要使用字符串方法或将输入或输出转换为字符串(在编写代码的每个版本中都遵循自我限制)。

上下文和用例

BigInt 在一些浏览器中可用,但没有 BigDecimal。 使用JavaScript编程语言应该可以将整数或小数转换为数组,以及将数组转换为整数或小数。在过程中,输入和输出不需要转换为字符串。

可以通过调整数组中第 n 位置的小数或整数来调整整数或小数的第 n 位数字,例如,直接尝试解决OEIS A217626问题。

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

可以通过引用数组的索引来操纵小数部分,然后将数组转换回数字。

当前规范正在进行中,涉及输入的小数部分处理时可能会存在一些挑战,特别是当有前导零时更为复杂。

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]

我尝试将整数或小数分散到一个数组中。将数字或整数转换为数组的函数必须能够转换为生成器函数,以实现此功能。

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

通过将函数设置为Number.prototype[Symbol.iterator]的值来实现功能,该函数名为numberToArray

最新版本的代码(其中某些概念和代码的原始版本基于使用JavaScript获取数字的小数部分; 不使用toString和parseInt方法将int值转换为String; 将整数转换为数字数组的问题和答案),这个版本有两个错误,即arrayToNumber的测试用例输出结果中100.05010000000497应为100.00015-83.082应为-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});

问题:

  1. 如何修复现有代码中列出的错误?
  2. 在不使用字符串方法或在过程中转换输入或输出为字符串的限制下,是否可以改进或以完全不同的方式组合代码以满足要求?
  3. 当前规范是否可以改进,以使使用的术语更加清晰,并避免对小数的预期输出产生混淆?

1
只是一个想法,123.001 被表示为 [[1,2,3],[0,0,1]],我可能误解了重点,但可以将其扩展到 123.001 -> [[1],[1,2,3],[0,0,1]]-123.001 -> [[0],[1,2,3],[0,0,1]],其中 [[修饰符],[整数],[小数]] - Bibberty
1个回答

8

方法 numberToArray():

我已经花了一些时间来研究你的实现,并决定首先分析numberToArray()方法。为此,我已决定创建一个方法来分析十进制数字并返回有关它的统计信息,基本上是您从代码的此部分获取的信息:

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

我所使用的方法是下一个(将在numberToArray()内使用),基本上获取以下信息: 1) 十进制数的整数部分(作为整数的iSection)。 2) 十进制数的小数部分(作为整数的dSection)。 3) 小数点后的位数(dDigits)。 4) 小数点后的前导零数(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;}

当然,如果您不喜欢,可以直接将同样的逻辑放在 numberToArray() 方法里。因此,在制作了先前的函数后,我对您的代码进行了一些重新组织,并添加了一些注释以帮助我理解您的操作。最终,在调整了您的代码之后,我发现错误映射到数组中主要是由于浮点数运算时的算术精度问题。经过一段时间的研究,我找到了一种基于使用数学 校正因子 的解决方案(当应用时,代码中有注释)。总的来说,到目前为止,我已经得出了下面这个对于 numberToArray() 方法的解决方案。

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()

对于这个问题,我决定自己尝试(事实上忽略了你现有的逻辑)。下一个方法将使用先前提到的 getDecimalStats(),并主要使用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;}

最后,我希望您能珍视我的努力,显然我的解决方案还有很多改进的空间(因此,欢迎任何建议)。例如,目前没有或很少进行安全检查。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接