使用有限的数学运算符查找多维数据集根

时间:2018-12-21 03:03:59

标签: javascript math big.js

我需要编写一个函数来告诉我数字是否是理想的立方体。如果是,我希望返回多维数据集根,否则返回false,如下所示:

cubeRoot(1)   // 1
cubeRoot(8)   // 2
cubeRoot(9)   // false
cubeRoot(27)  // 3
cubeRoot(28)  // false

它必须适用于很大的数字。性能是一大优势。

但是,我正在使用的库意味着我只能使用以下数学函数/运算符:

abs, round, sqrt
/ * + -
===
> >= < <=
%
^

如果仅使用上述运算符在JS中提供了答案,我就可以自己(我正在使用的库)将答案转换为(big.js)语法。这可能吗?

由于其保证的精度,我需要使用big.js

6 个答案:

答案 0 :(得分:3)

您可以使用JS内置BigInt。我假设输入是正整数。对于while循环,我提供了时间复杂度近似值,其中 n 是输入的十进制数字。此版本的答案受到Salix alba answerwiki "cube root"的启发:

  1. Binary search O(n log2(10)/ 3) <= O(1.11 * n)(对于n = 1000我得到1110次迭代-测试here,-测试l=0r=a O(10/3 n)

    function cubicRoot(a) 
    { 
      let d = Math.floor((a.toString(2).length-1)/3); // binary digits nuber / 3
      let r = 2n ** BigInt(d+1); // right boundary approximation
      let l = 2n ** BigInt(d);   // left boundary approximation
      let x=BigInt(l); 
      let o=BigInt(0);           // old historical value
      
      while(1) {
        o = x;
        y = x * x * x;      
        y<a ? l=x : r=x;      
        if(y==a) return x;
        x = l + (r - l)/2n;      
        if(o==x) return false;
      }
    }
    
    // TEST
    
    let t = "98765432109876543210987654321098765432109876543210987";
    let B = BigInt(t) * BigInt(t) * BigInt(t);
    
    console.log('cRoot(B):   ', cubicRoot( B )     .toString());
    console.log('cRoot(B+1): ', cubicRoot( B +1n ) .toString());
    console.log('cRoot(B-1): ', cubicRoot( B -1n ) .toString());
    console.log('B=',B.toString().split('').map((x,i)=>(i%60?'':'\n')+x).join('')); // split long number to multiline string

  2. Newton-Raphson方案 O(log(9n))(对于n <= 1000,我最多获得13次迭代test)。我对“停止”条件有疑问-对于数字a=b*b*b - 1,我需要检查x的两个历史值(如果它们至少出现一次,则停止),但是我不知道在某些情况下我们应该检查树或更多历史值以停止算法。

    function cubicRoot(a) 
    { 
      let d = Math.floor((a.toString(2).length-1)/3); // binary digits nuber / 3
      let x = 2n ** BigInt(d);    
      let o=BigInt(0); // last history value
      let u=BigInt(0); // pre-last history value
      let i=0; // loop counter for worst scenario stop condition
      
      while(i<d*4) {
        i++;
        u = o;
        o = x;     
        y = x*x*x;            
        if(y===a) return x;
        x = ( a / (x*x) + 2n* x ) / 3n;
        if(o==x || u==x) return false; 
      }
      
      return false; // worst scenario - if for some case algorithm not finish after d*4 iterations
    }
    
    // TEST
    
    let t = "98765432109876543210987654321098765432109876543210987";
    let B = BigInt(t) * BigInt(t) * BigInt(t);
    
    console.log('cRoot(B):   ', cubicRoot( B )     .toString());
    console.log('cRoot(B+1): ', cubicRoot( B +1n ) .toString());
    console.log('cRoot(B-1): ', cubicRoot( B -1n ) .toString());
    console.log('B=',B.toString().split('').map((x,i)=>(i%60?'':'\n')+x).join('')); // split long number to multiline string

  3. Halley method O(log(3n))(对于经过测试的n <= 1000个数字,我最多可获得8次迭代-test

    < div class =“ snippet” data-lang =“ js” data-hide =“ true” data-console =“ true” data-babel =“ false”>
    function cubicRoot(a) 
    { 
      let d = Math.floor((a.toString(2).length-1)/3); // binary digits nuber / 3
      let x = 2n ** BigInt(d);    
      let o=BigInt(0); // last history value
      let i=0; // loop counter for worst scenario stop condition
      
      while(i<d) {
        i++;
        o = x;     
        y = x*x*x;            
        if(y==a) return x;
        x = 1n + x*(y + 2n*a)/(2n*y + a);
        if(o==x) return false; 
      }
      
      return false; // worst scenario (??)
    }
    
    // TEST
    
    let t = "98765432109876543210987654321098765432109876543210987";
    let B = BigInt(t) * BigInt(t) * BigInt(t);
    
    console.log('cRoot(B):   ', cubicRoot( B )     .toString());
    console.log('cRoot(B+1): ', cubicRoot( B +1n ) .toString());
    console.log('cRoot(B-1): ', cubicRoot( B -1n ) .toString());
    console.log('B=',B.toString().split('').map((x,i)=>(i%60?'':'\n')+x).join('')); // split long number to multiline string

答案 1 :(得分:3)

这是与KamilKiełczewski代码相同思想的另一个版本,但被采用来使用big.js API并依赖于其实现细节。

function isZero(v) {
    let digits = v.c;
    return digits.length === 1 && digits[0] === 0;
}

function isInteger(v) {
    if (isZero(v))
        return true;
    return v.c.length <= v.e + 1;
}

function neg(v) {
    return new Big(0).minus(v);
}


function cubeRoot(v) {
    const ZERO = Big(0);
    const TEN = new Big(10);

    let c0 = v.cmp(ZERO);
    if (c0 === 0)
        return ZERO;
    if (c0 < 0) {
        let abs3 = cubeRoot(v.abs());
        if (abs3 instanceof Big)
            return neg(abs3);
        else
            return abs3;
    }

    if (!isInteger(v))
        return false;

    // use 10 because it should be fast given the way the value is stored inside Big
    let left = TEN.pow(Math.floor(v.e / 3));
    if (left.pow(3).eq(v))
        return left;

    let right = left.times(TEN);

    while (true) {
        let middle = left.plus(right).div(2);
        if (!isInteger(middle)) {
            middle = middle.round(0, 0); // round down
        }
        if (middle.eq(left))
            return false;
        let m3 = middle.pow(3);
        let cmp = m3.cmp(v);
        if (cmp === 0)
            return middle;
        if (cmp < 0)
            left = middle;
        else
            right = middle;
    }
}

此代码背后的主要思想是使用二进制搜索,但搜索的开始对leftright的估计要比Kamil的代码好一些。特别地,它依赖于以下事实:Big以标准化的指数表示形式存储值:作为十进制数字和指数的数组。因此,我们可以轻松找到n这样的10^n <= cubeRoot(value) < 10^(n+1)。此技巧应减少循环的几次迭代。可能使用Newton-Raphson iteration而不是简单的二进制搜索可能会更快一些,但我认为实际上您看不出有什么区别。

答案 2 :(得分:3)

所以让我们看一些二进制的立方体

2^3 = 8 = 100b (3 binary digits)
4^3 = 64 = 100 000b  (6 binary digits)
8^3 = 512 = 100 000 000b (9 binary digits)
(2^n)^3 = 2^(3n) = (3n binary digits).

因此,对于一个粗略的第一次猜测,请计算二进制数字的数量,d,然后除以三,让n = d/3告诉我们多维数据集的根数是否在2^n2^(n+1)。可以将计数数字链接到对数的原始第一近似。

如果无法访问二进制数字,只需将其反复除以8(或8的幂),直到结果为零即可。

现在,我们可以使用Newton-Raphson解决方案了。 Wikipedia cube root会为我们提供迭代公式。如果a是我们要查找其根的数字,而x_0是我们使用上述方法的第一个猜测

x_{n+1} = ( a / x_n^2 + 2 x_n ) / 3.

这可以很快收敛。例如,对于a=12345678901234567890,我们发现a在8 ^ 21和8 ^ 22之间,因此多维数据集根必须在2 ^ 21和2 ^ 22之间。

运行迭代

x_1 = 2333795, x_1^3 = 12711245751310434875 
x_2 = 2311422, x_2^3 = 12349168818517523448
x_3 = 2311204, x_3^3 = 12345675040784217664
x_4 = 2311204, x_4^3 = 12345675040784217664

,我们看到它在经过三个迭代之后已经收敛。检查显示a在2311204 ^ 3和2311205 ^ 3之间。

此算法可以与使用big.js的计算一起运行。以上计算是使用Java的BigInt类完成的。

答案 3 :(得分:1)

我发现this great answer,它表明我对算法做了些微修改。这是您可以做的:

function simpleCubeRoot(x) {    
    if (x === 0) {
        return 0;
    }
    if (x < 0) {
        return -simpleCubeRoot(-x);
    }

    var r = x;
    var ex = 0;

    while (r < 0.125) { 
        r *= 8; ex--; 
    }
    while (r > 1.0) { 
        r *= 0.125; ex++; 
    }

    r = (-0.46946116 * r + 1.072302) * r + 0.3812513;

    while (ex < 0) { 
        r *= 0.5; ex++; 
    }
    while (ex > 0) { 
        r *= 2; ex--; 
    }

    r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);
    r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);
    r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);
    r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);

    if (Number.isInteger(r)) {
        return r;
    }
    return false;
}

演示:

function simpleCubeRoot(x) {
    if (x === 0) {
        return 0;
    }
    if (x < 0) {
        return -simpleCubeRoot(-x);
    }

    var r = x;
    var ex = 0;

    while (r < 0.125) {
        r *= 8;
        ex--;
    }
    while (r > 1.0) {
        r *= 0.125;
        ex++;
    }

    r = (-0.46946116 * r + 1.072302) * r + 0.3812513;

    while (ex < 0) {
        r *= 0.5;
        ex++;
    }
    while (ex > 0) {
        r *= 2;
        ex--;
    }

    r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);
    r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);
    r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);
    r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);

    if (Number.isInteger(r)) {
        return r;
    }
    return false;
}

console.log(simpleCubeRoot(27)); //Should return 3
console.log(simpleCubeRoot(0)); //Should return 0

答案 4 :(得分:1)

据我所知,只有通过Math.pow才能通过数学库访问Javascript中的指数。

使用指数,x的立方根可以由cubeRoot(x) = x^(1/3)计算。在使用Math的javascript中,外观类似于var cubeRoot = Math.pow(x, 1/3)

由于如果结果为分数,函数必须返回false,所以我将使用Math.round比较立方根。您的函数将如下所示:

function cubeRoot(x) {
    var root = Math.pow(x, 1/3);
    if (Math.round(root) !== root) {
        return false;
    }
    return root;
}

但是,由于1/3实际上是0.33333...,具有一定的浮动精度,因此这不适用于大型多维数据集。例如,Math.pow(45629414826904, 1/3)可能会给您返回类似35733.99999999998的信息。

然后我要做的是,如果四舍五入后的结果之间的差异很小(例如小于1/1000000),请重新计算该数字以查看是否能使您退回原来的x

function cubeRoot(x) {
    var root = Math.pow(x, 1/3);
    var roundedRoot = Math.round(root);
    var diff = Math.abs(root - roundedRoot);

    if (diff <= 1/1000000) {
        var reCubed = Math.pow(roundedRoot, 3);
        if (reCubed === x) {
           return roundedRoot;
        }
        return false;
    }
    if (diff !== roundedRoot) {
        return false;
    }
    return root;
}

我在y本地Nodejs上进行了一些测试,似乎可以处理大小为8000120000600001(或200001^3)的多维数据集,然后才能在某些非多维数据集上返回false。尚未对其进行广泛的测试,但是鉴于您的问题的局限性,这是我能想到的最好的方法。

答案 5 :(得分:0)

为避免引起立方根头痛,您可以使用big.js的亲戚decimal.js-light(单独或与big.js一起使用)

big.js不支持小数幂,但是decimal.js-light则支持小数幂,如下所示:

const Big = require('big.js')
const Decimal = require('decimal.js-light')

const nthRoot = (bigNumber, intRoot) => {
  const strBigNumber = bigNumber.toFixed()
  const decimal = Decimal(strBigNumber)
  const root = decimal.pow(1 / intRoot)
  return Big(root.toFixed())
}

module.exports = nthRoot

并使用如下:

nthRoot(Big(8), 3)          // 1.9999999999999998613
nthRoot(Big(8), 3).round()  // 2
相关问题
最新问题