16

I want to generate the digits of the square root of two to 3 million digits.

I am aware of Newton-Raphson but I don't have much clue how to implement it in C or C++ due to lack of biginteger support. Can somebody point me in the right direction?

Also, if anybody knows how to do it in python (I'm a beginner), I would also appreciate it.

Jason Sundram
  • 12,225
  • 19
  • 71
  • 86
Quixotic
  • 2,424
  • 7
  • 36
  • 58
  • With the [GMP](http://gmplib.org/) package you can easily add large number support. GMP is quite mature and highly optimised for an enormous range of numbers, supports integers, rationals, and floats, and has C++ wrappers for the low-level C stuff. – President James K. Polk Mar 03 '11 at 23:02
  • 2
    I've written some python code based on this: http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Digit-by-digit_calculation. I tested it, though, and I doubt it could handle three-million digits very quickly. Still, if you're interested, I'll post it. – senderle Mar 03 '11 at 23:10
  • @senderle:Sure,I guess this is the same method we learned in middle school right? – Quixotic Mar 03 '11 at 23:12
  • 2
    Well the method I learned in middle school involved pushing buttons on a calculator :) – senderle Mar 03 '11 at 23:27
  • Wolfram Alpha wont let you calculate 3 million digits but [what you can type in is amazing](http://www.wolframalpha.com/input/?i=square+root+of+two+with+10+thousand+digits). – Jochen Ritzel Mar 03 '11 at 23:29
  • @senderle:LOL,I learned that method in middle school then with calculator in college and today I relearned that method again. – Quixotic Mar 03 '11 at 23:30
  • @senderle:It's really late here .. are you posting your solution? :) – Quixotic Mar 03 '11 at 23:32
  • @Philando, sorry! It's up now. – senderle Mar 03 '11 at 23:44
  • 6
    I hope you're not planning to just post someone else's solution at http://www.spoj.pl/problems/SQRT2/ without trying to learn the algorithm. – rettvest Mar 04 '11 at 09:53

9 Answers9

7

You could try using the mapping:

a/b -> (a+2b)/(a+b) starting with a= 1, b= 1. This converges to sqrt(2) (in fact gives the continued fraction representations of it).

Now the key point: This can be represented as a matrix multiplication (similar to fibonacci)

If a_n and b_n are the nth numbers in the steps then

[1 2] [a_n b_n]T = [a_(n+1) b_(n+1)]T
[1 1]

which now gives us

[1 2]n [a_1 b_1]T = [a_(n+1) b_(n+1)]T
[1 1]

Thus if the 2x2 matrix is A, we need to compute An which can be done by repeated squaring and only uses integer arithmetic (so you don't have to worry about precision issues).

Also note that the a/b you get will always be in reduced form (as gcd(a,b) = gcd(a+2b, a+b)), so if you are thinking of using a fraction class to represent the intermediate results, don't!

Since the nth denominators is like (1+sqrt(2))^n, to get 3 million digits you would likely need to compute till the 3671656th term.

Note, even though you are looking for the ~3.6 millionth term, repeated squaring will allow you to compute the nth term in O(Log n) multiplications and additions.

Also, this can easily be made parallel, unlike the iterative ones like Newton-Raphson etc.

  • I guess this + FFT would give a very fast solution.Thanks :-) – Quixotic Mar 04 '11 at 21:30
  • @Philando: I would presume the BigInt packages will be doing FFT already. Good luck :-) –  Mar 04 '11 at 21:52
  • Unknowingly I was using the above here: http://stackoverflow.com/a/40982112/502187 , just observe [[1,2],[1,1]]*[[1,2],[1,1]] = [[3,4],[2,3]]. –  Jan 30 '17 at 19:41
6

EDIT: I like this version better than the previous. It's a general solution that accepts both integers and decimal fractions; with n = 2 and precision = 100000, it takes about two minutes. Thanks to Paul McGuire for his suggestions & other suggestions welcome!

def sqrt_list(n, precision):
    ndigits = []        # break n into list of digits
    n_int = int(n)
    n_fraction = n - n_int

    while n_int:                            # generate list of digits of integral part
        ndigits.append(n_int % 10)
        n_int /= 10
    if len(ndigits) % 2: ndigits.append(0)  # ndigits will be processed in groups of 2

    decimal_point_index = len(ndigits) / 2  # remember decimal point position
    while n_fraction:                       # insert digits from fractional part
        n_fraction *= 10
        ndigits.insert(0, int(n_fraction))
        n_fraction -= int(n_fraction)
    if len(ndigits) % 2: ndigits.insert(0, 0)  # ndigits will be processed in groups of 2

    rootlist = []
    root = carry = 0                        # the algorithm
    while root == 0 or (len(rootlist) < precision and (ndigits or carry != 0)):
        carry = carry * 100
        if ndigits: carry += ndigits.pop() * 10 + ndigits.pop()
        x = 9
        while (20 * root + x) * x > carry:
                x -= 1
        carry -= (20 * root + x) * x
        root = root * 10 + x
        rootlist.append(x)
    return rootlist, decimal_point_index
senderle
  • 145,869
  • 36
  • 209
  • 233
  • Couldn't you replace `while root == 0 or (ceil(log(root, 10)) < precision and (ndigits or carry != 0)):` with `while (len(rootlist) < precision and (ndigits or carry != 0)):`? Gets rid of those nasty ceil and log function calls, and the repeated test for `root == 0`. – PaulMcG Mar 04 '11 at 05:45
  • @Paul, IIRC from testing, there are inputs that generate `precision` zeroes and _then_, a few digits later, start generating nonzero values. So I wanted to make sure to get those nonzero values. I'll try using `while len(rootlist)...` though; `rootlist` was a late addition and I guess I never got around to refactoring the surrounding code. – senderle Mar 04 '11 at 06:27
  • @Paul, a quick test indicates that (surprisingly) `len(rootlist)` is only a few microseconds slower than `log10(root)` even for large `precision`. But `len` is indeed more aesthetically pleasing, so I changed it. Thanks again. – senderle Mar 04 '11 at 14:46
  • @senderle - I suspect the time is going to the method resolution and/or global function lookup. You can squeeze a few more microseconds by saving repeated global and instance functions into local variables, so you only pay the name resolution penalty once. Some candidates: len, ndigits.append, ndigits.pop, rootlist.append. You can also lift ``range(1,10)`` out of the main algorithm loop to a constant variable like ``_1_to_9`` - no sense in having to make that range call every time thru the loop. – PaulMcG Mar 04 '11 at 15:32
  • @Paul, thanks -- in fact, I just realized the `for` loop and `range` call are altogether unnecessary. Testing indicates that the `while` loop above is fastest. – senderle Mar 06 '11 at 18:55
  • Have you verified this with the NASA file? 2 mins to compute 100k? HOLY SHOOT. What about 1 million? – CppLearner Mar 26 '12 at 02:49
  • @CppLearner, I did, once you pointed it out to me. But I suspect for this specific case, [the accepted answer](http://stackoverflow.com/a/5196280/577088) will be even faster. – senderle Apr 18 '12 at 02:50
3

As for arbitrary big numbers you could have a look at The GNU Multiple Precision Arithmetic Library (for C/C++).

Morten Kristensen
  • 7,412
  • 4
  • 32
  • 52
3

For work? Use a library!

For fun? Good for you :)

Write a program to imitate what you would do with pencil and paper. Start with 1 digit, then 2 digits, then 3, ..., ...

Don't worry about Newton or anybody else. Just do it your way.

pmg
  • 106,608
  • 13
  • 126
  • 198
3

Here is a short version for calculating the square root of an integer a to digits of precision. It works by finding the integer square root of a after multiplying by 10 raised to the 2 x digits.

def sqroot(a, digits):
    a = a * (10**(2*digits))
    x_prev = 0
    x_next = 1 * (10**digits)
    while x_prev != x_next:
        x_prev = x_next
        x_next = (x_prev + (a // x_prev)) >> 1
    return x_next

Just a few caveats.

You'll need to convert the result to a string and add the decimal point at the correct location (if you want the decimal point printed).

Converting a very large integer to a string isn't very fast.

Dividing very large integers isn't very fast (in Python) either.

Depending on the performance of your system, it may take an hour or longer to calculate the square root of 2 to 3 million decimal places.

I haven't proven the loop will always terminate. It may oscillate between two values differing in the last digit. Or it may not.

casevh
  • 11,093
  • 1
  • 24
  • 35
3

The nicest way is probably using the continued fraction expansion [1; 2, 2, ...] the square root of two.

def root_two_cf_expansion():
    yield 1
    while True:
        yield 2

def z(a,b,c,d, contfrac):
    for x in contfrac:
        while a > 0 and b > 0 and c > 0 and d > 0:
            t = a // c
            t2 = b // d
            if not t == t2:
                break
            yield t
            a = (10 * (a - c*t))
            b = (10 * (b - d*t))
            # continue with same fraction, don't pull new x
        a, b = x*a+b, a
        c, d = x*c+d, c
    for digit in rdigits(a, c):
        yield digit

def rdigits(p, q):
    while p > 0:
        if p > q:
           d = p // q
           p = p - q * d
        else:
           d = (10 * p) // q
           p = 10 * p - q * d
        yield d

def decimal(contfrac):
    return z(1,0,0,1,contfrac)

decimal((root_two_cf_expansion()) returns an iterator of all the decimal digits. t1 and t2 in the algorithm are minimum and maximum values of the next digit. When they are equal, we output that digit.

Note that this does not handle certain exceptional cases such as negative numbers in the continued fraction.

(This code is an adaptation of Haskell code for handling continued fractions that has been floating around.)

wnoise
  • 9,764
  • 37
  • 47
2

Well, the following is the code that I wrote. It generated a million digits after the decimal for the square root of 2 in about 60800 seconds for me, but my laptop was sleeping when it was running the program, it should be faster that. You can try to generate 3 million digits, but it might take a couple days to get it.

def sqrt(number,digits_after_decimal=20):
import time
start=time.time()
original_number=number
number=str(number)
list=[]
for a in range(len(number)):
    if number[a]=='.':
        decimal_point_locaiton=a
        break
    if a==len(number)-1:
        number+='.'
        decimal_point_locaiton=a+1
if decimal_point_locaiton/2!=round(decimal_point_locaiton/2):
    number='0'+number
    decimal_point_locaiton+=1
if len(number)/2!=round(len(number)/2):
    number+='0'
number=number[:decimal_point_locaiton]+number[decimal_point_locaiton+1:]
decimal_point_ans=int((decimal_point_locaiton-2)/2)+1
for a in range(0,len(number),2):
    if number[a]!='0':
        list.append(eval(number[a:a+2]))
    else:
        try:
            list.append(eval(number[a+1]))
        except IndexError:
            pass
p=0
c=list[0]
x=0
ans=''
for a in range(len(list)):
    while c>=(20*p+x)*(x):
        x+=1
    y=(20*p+x-1)*(x-1)
    p=p*10+x-1
    ans+=str(x-1)
    c-=y
    try:
        c=c*100+list[a+1]
    except IndexError:
        c=c*100
while c!=0:
    x=0
    while c>=(20*p+x)*(x):
        x+=1
    y=(20*p+x-1)*(x-1)
    p=p*10+x-1
    ans+=str(x-1)
    c-=y
    c=c*100
    if len(ans)-decimal_point_ans>=digits_after_decimal:
            break
ans=ans[:decimal_point_ans]+'.'+ans[decimal_point_ans:]
total=time.time()-start
return ans,total
8xlx8
  • 21
  • 2
0

Python already supports big integers out of the box, and if that's the only thing holding you back in C/C++ you can always write a quick container class yourself.

The only problem you've mentioned is a lack of big integers. If you don't want to use a library for that, then are you looking for help writing such a class?

Michael Burge
  • 425
  • 9
  • 16
0

Here's a more efficient integer square root function (in Python 3.x) that should terminate in all cases. It starts with a number much closer to the square root, so it takes fewer steps. Note that int.bit_length requires Python 3.1+. Error checking left out for brevity.

def isqrt(n):
    x = (n >> n.bit_length() // 2) + 1
    result = (x + n // x) // 2
    while abs(result - x) > 1:
        x = result
        result = (x + n // x) // 2
    while result * result > n:
        result -= 1
    return result
Vamana
  • 580
  • 2
  • 7
  • The OP isn't asking for an integral square root function; you're responding to casevh instead of the OP. I think you should edit this to give an answer to the question asked. – senderle Mar 04 '11 at 15:03
  • @senderle: Sorry; I figured this function could just be dropped into the other solution. Was going to take your suggestion, but in the light of @stubbscroll's comment above, I think I'll let the OP do the work :) – Vamana Mar 05 '11 at 12:25