13

This concerns the new JavaScript BigInt type, as supported in Chrome and Node v10.4

Both the following lines throw an error:

Math.sqrt(9n)
Math.sqrt(BigInt(9))

Error is:

Cannot convert a BigInt value to a number

How do I get the square root of a BigInt in JavaScript? TIA

danday74
  • 52,471
  • 49
  • 232
  • 283

3 Answers3

16

From here: https://golb.hplar.ch/2018/09/javascript-bigint.html

function sqrt(value) {
    if (value < 0n) {
        throw 'square root of negative numbers is not supported'
    }

    if (value < 2n) {
        return value;
    }

    function newtonIteration(n, x0) {
        const x1 = ((n / x0) + x0) >> 1n;
        if (x0 === x1 || x0 === (x1 - 1n)) {
            return x0;
        }
        return newtonIteration(n, x1);
    }

    return newtonIteration(value, 1n);
}

sqrt(BigInt(9))
kopaty4
  • 2,236
  • 4
  • 26
  • 39
  • 2
    For initial guess (`1n` is passed to the first call of `newtonIteration`) it could be better to use something like `const guess = 1n + (sqrt(value >> BigInt(Math.floor(bitLength(value) / 2))) << BigInt(Math.floor(bitLength(value) / 4)));`, where `bitLength` is defined as `function bitLength(n) { return n.toString(16).length * 4; }`. This could make it faster for very big integers. – 4esn0k May 20 '19 at 09:36
  • 1
    This only works for whole number square roots, unfortunately. – Jeff Lowery Jul 06 '19 at 00:02
  • 4
    Just an fyi, passing in `4n` seems to give `1n`, not `2n` as expected. Seems the iteration bias is causing this, i.e., the `x0 === (x1 - 1n)`. This appears to be the only erroneous case; so perhaps making an additional special case for it would suffice. – Codesmith Jun 08 '20 at 21:42
  • 1
    Certain initial guesses, such as what @4esn0k mentioned, you need to `return x1` instead of `return x0`, with the caveat that sqrt (N^2 - 1) returns N. – okzoomer Sep 13 '21 at 15:18
3

General case: n-th root

Here is more general solution for n-th root

/**
 * Calculate n-th root of val
 * Parameters:
 * k: is n-th (default sqare root)
 * limit: is maximum number of iterations (default: -1 no limit)
 */
function rootNth(val, k=2n, limit=-1) {
    let o = 0n; // old approx value
    let x = val;
    
    while(x**k!==k && x!==o && --limit) {
      o=x;
      x = ((k-1n)*x + val/x**(k-1n))/k;
      if(limit<0 && (x-o)**2n == 1n) break;
    }
    
    if ((val-(x-1n)**k)**2n < (val-x**k)**2n) x=x-1n;
    if ((val-(x+1n)**k)**2n < (val-x**k)**2n) x=x+1n;
    return x;
}

let v = 1000000n;
console.log(`root^3 form ${v} = ${rootNth(v,3n)}` );
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
  • 1
    this function return a wrong result if it do more than `limit` iterations. It should better throw an error. – Yukulélé Nov 22 '20 at 20:17
  • The problem noted by @Yukulélé occurs even for small values of `value` because `o` and `x` oscillate between `R` and `R+1`, where `R` is the correct root, when `value` is 1 less than a perfect square. For such numbers, the returned value will be 1 larger than the correct root 50% of the time. I added `if(x>o) return o;` as the third line in the while loop to fix this issue. For legit cases where the limit is exceeded. an error would still be appropriate. I am not sure that this solution is correct, though, as I don't understand the algorithm. – Jared Brandt Nov 17 '22 at 00:24
  • @JaredBrandt - can you give some examples / test cases? (link to algorithm is in answer) – Kamil Kiełczewski Nov 18 '22 at 00:42
  • 1
    @KamilKiełczewski Thanks, I missed the link! I studied it and agree it is implemented correctly, but for some reason it doesn't behave with integers, and I haven't studied it enough to know why. I copied the code directly into a fiddle, changed the cube root to square root, the radicand to (5^2-1), and added a console.log() in the while loop. You can see it oscillates between 4 and 5, but incorrectly settles on 5. Changing the radicand to (3^2-1) oscillates between 2 and 3, and correctly settles on 2. Fiddle: https://jsfiddle.net/cf2y84nr/ – Jared Brandt Nov 22 '22 at 03:57
  • @JaredBrandt I modify answer. Now I assume that algorithm gives value which can be smaller or greater than right value by 1. So I test two cases: when x-1 is better than x, and if x+1 is better than x - and choose better solution as x. (instead using `Math.abs` i compare squares `**2n`). I also add automagical calculation of limit (which is also speed optimisation) – Kamil Kiełczewski Nov 22 '22 at 06:42
  • @KamilKiełczewski I do believe this has become unnecessarily complicated. May I have your blessing to edit the answer? – Jared Brandt Nov 24 '22 at 01:17
  • @JaredBrandt if you can simplify - feel free to do it – Kamil Kiełczewski Nov 24 '22 at 21:20
2

There is an npm library bigint-isqrt, seem to work ok. It returns the floor value if there is no integer root.

const sqrt = require('bigint-isqrt');
> sqrt(1023n);
31n
> sqrt(1024n);
32n

Though it's still a mystery for me how magic numbers like value < 16n and 1n << 52n in it's implementation help finding a square root. Judging from a PR it's some sort of an approximation heuristic, I wonder whether it's more efficient than algorithms in other answers...

Klesun
  • 12,280
  • 5
  • 59
  • 52