Generating digits of square root of 2
Asked Answered
S

9

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.

Slippy answered 3/3, 2011 at 22:56 Comment(9)
With the GMP 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.Burgos
I've written some python code based on this: en.wikipedia.org/wiki/…. I tested it, though, and I doubt it could handle three-million digits very quickly. Still, if you're interested, I'll post it.Guillot
@senderle:Sure,I guess this is the same method we learned in middle school right?Slippy
Well the method I learned in middle school involved pushing buttons on a calculator :)Guillot
Wolfram Alpha wont let you calculate 3 million digits but what you can type in is amazing.Disenchant
@senderle:LOL,I learned that method in middle school then with calculator in college and today I relearned that method again.Slippy
@senderle:It's really late here .. are you posting your solution? :)Slippy
@Philando, sorry! It's up now.Guillot
I hope you're not planning to just post someone else's solution at spoj.pl/problems/SQRT2 without trying to learn the algorithm.Jerz
C
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.

Cacus answered 4/3, 2011 at 16:8 Comment(3)
I guess this + FFT would give a very fast solution.Thanks :-)Slippy
@Philando: I would presume the BigInt packages will be doing FFT already. Good luck :-)Cacus
Unknowingly I was using the above here: https://mcmap.net/q/749253/-find-as-many-digits-of-the-square-root-of-2-as-possible , just observe [[1,2],[1,1]]*[[1,2],[1,1]] = [[3,4],[2,3]].Hundley
G
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
Guillot answered 3/3, 2011 at 23:33 Comment(7)
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.Allusion
@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.Guillot
@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.Guillot
@Guillot - 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.Allusion
@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.Guillot
Have you verified this with the NASA file? 2 mins to compute 100k? HOLY SHOOT. What about 1 million?Bakery
@CppLearner, I did, once you pointed it out to me. But I suspect for this specific case, the accepted answer will be even faster.Guillot
N
3

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

Nazi answered 3/3, 2011 at 22:59 Comment(3)
Sorry,library is not something I am looking for .. can you state a python solution ?Slippy
@Philando: Then why did you tag this C/C++?Swob
@Philando: I think you should have a look at this page: en.wikipedia.org/wiki/….Nazi
P
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.

Portiere answered 3/3, 2011 at 23:4 Comment(7)
Not just fun :=)... I also want a fast solution too ;-)Slippy
Then, try a solution that yields 1 digit, then 2, then 4, then 8, etc. Or an even faster converging one.Whisler
What does it matter if your calculation takes 24 hours instead of half a nanosecond? ???? It is your calculation. And the next version will take only 8 hours ...Portiere
It's not just digits only (else there are so many site are there) I want a moderately fast solution that is possible without using very sophisticated computers :-)Slippy
lol, a fast solution using unsophisticated computers. Depends on how you qualify the term fast.Rosanarosane
@Thomas Matthews:How about 3 million in about 20-25 seconds ?! :PSlippy
@Thomas Or unsophisticated for that matterRound
U
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.

Unearth answered 4/3, 2011 at 5:0 Comment(0)
A
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.)

Amie answered 4/3, 2011 at 6:5 Comment(0)
O
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
Offence answered 9/8, 2011 at 22:24 Comment(0)
S
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?

Secondly answered 3/3, 2011 at 23:16 Comment(0)
K
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
Kuwait answered 4/3, 2011 at 11:40 Comment(2)
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.Guillot
@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 :)Kuwait

© 2022 - 2024 — McMap. All rights reserved.