The other day I was thinking about approximating in terms of segments of size for , The question I was interested was "What is the least number of segments of length that we need to cross over starting from zero?" and one particular sequence of integers I came up with was this:
Sadly, at the time of writing this post the Online Encyclopedia of Integer Sequences did not return anything for this sequence.
The sequence represents the numerators for dyadic rationals which approximate within precision (within k-bits of precision), but is this sequence computable? In what follows I assume that the reader is familiar with the basic definitions of mu-recursive functions.
It turns out that our sequence can be described by a recursive function
for all .
The idea of the function is that for each it outputs the number of segments that fit into plus one.
Since for each , the value is the smallest integer such that we have
for each .
From this we obtain the error bound .
Approximation of within precision can be "represented" by a natural number . To get the actual approximation by a dyadic rational just divide by but what we want is to avoid division. We want to calculate only with natural numbers!
Implementation
The function f has a straightforward (although inefficient) implementation in Python.
def calcSqrt2Segments(k): n = 0 while(n*n <= 2**(2*k+1)): n = n + 1 return n
This is in fact terribly inefficient, but in computability theory efficiency is not the goal, the goal is usually to prove that something is uncomputable even if you have all the resources of time and space at your disposal. Nevertheless, here is a slightly better version we get if we notice that the next term is about twice as large as with a small correction.
def calcSqrt2segmentsRec(k): if k == 0: return 2 else: res = 2*calcSqrt2segmentsRec(k-1) - 1 if res*res <= 2**(2*k+1): res = res + 1 return res
This of course suffers from stack overflow problems, so memoization is a natural remedy.
def calcSqrt2segmentsMemo(k): m = dict() m[0] = 2 for j in range(1, k+1): m[j] = 2*m[j-1] - 1 if m[j]*m[j] <= 2**(2*j+1): m[j] = m[j] + 1 return m[k]
Memoization can use-up memory and we can do better. We don't need to memorize the whole sequence up to k to calculate we only need the value .
def calcSqrt2segmentsBest(k): m = 2 for j in range(1, k+1): m = 2*m - 1 if m*m <= 2**(2*j+1): m = m + 1 return m
After defining a simple timing function and testing our memoization and "best" version we see that the best version is not so good in terms of running time when compared with memoization, they are both approximately about the same in terms of speed (the times are in seconds):
>>> timing(calcSqrt2segmentsMemo, 10000) 0.6640379428863525 >>> timing(calcSqrt2segmentsBest, 10000) 0.6430368423461914 >>> timing(calcSqrt2segmentsMemo, 20000) 3.3311898708343506 >>> timing(calcSqrt2segmentsBest, 20000) 3.3061890602111816 >>> timing(calcSqrt2segmentsMemo, 30000) 8.899508953094482 >>> timing(calcSqrt2segmentsBest, 30000) 8.848505973815918 >>> timing(calcSqrt2segmentsMemo, 50000) 30.04171895980835 >>> timing(calcSqrt2segmentsBest, 50000) 29.96371293067932 >>> timing(calcSqrt2segmentsMemo, 100000) 164.99343705177307 >>> timing(calcSqrt2segmentsBest, 100000) 168.6026430130005
Neverteless, we can use our "best" program to calculate to arbritrary number of decimal places (in base 10). First, we need to determine how many bits are sufficient to calculate to decimal places in base 10. Simple calculation yields:
In Python we need a helper function that calculates this bound that avoids the nasty log and ceiling functions, so here it is:
def calcNumDigits(n): res = 1 for j in range(2, n+1): if( j%3 == 0 or j%3 == 1 ): res = res + 3 else: res = res + 4 return res
At last, calculating to arbritrary precision is done with this simple code below, returning a String with the digits of in base 10. Note once more, that all this calculation is done only with the natural numbers and here we are using Python's powerful implementation of arithmetic with arbritrary long integers (which is not as nicely supported for decimals at least at the time of writing this post).
Note also, that instead of dividing by we are multiplying it with to get a natural number with all of it's digits being an approximation of with k bits of precision. This natural number we are then converting to a string, inserting a decimal point '.' and returning this as our result. So, here is the code:
def calcSqrt2(n): k = calcNumDigits(n) res = str(5**k * calcSqrt2segmentsBest(k)) return res[0:1] + '.' + res[1:n]
Running it gives us nice results:
>>> calcSqrt2(30) '1.41421356237309504880168872421' >>> calcSqrt2(300) '1.41421356237309504880168872420969807856967187537694807317667973799073247846210703885038753432764157273501384623091229702492483605585073721264412149709993583141322266592750559275579995050115278206057147010955997160597027453459686201472851741864088919860955232923048430871432145083976260362799525140799' >>> calcSqrt2(3000) '1.41421356237309504880168872420969807856967187537694807317667973799073247846210703885038753432764157273501384623091229702492483605585073721264412149709993583141322266592750559275579995050115278206057147010955997160597027453459686201472851741864088919860955232923048430871432145083976260362799525140798968725339654633180882964062061525835239505474575028775996172983557522033753185701135437460340849884716038689997069900481503054402779031645424782306849293691862158057846311159666871301301561856898723723528850926486124949771542183342042856860601468247207714358548741556570696776537202264854470158588016207584749226572260020855844665214583988939443709265918003113882464681570826301005948587040031864803421948972782906410450726368813137398552561173220402450912277002269411275736272804957381089675040183698683684507257993647290607629969413804756548237289971803268024744206292691248590521810044598421505911202494413417285314781058036033710773091828693147101711116839165817268894197587165821521282295184884720896946338628915628827659526351405422676532396946175112916024087155101351504553812875600526314680171274026539694702403005174953188629256313851881634780015693691768818523786840522878376293892143006558695686859645951555016447245098368960368873231143894155766510408839142923381132060524336294853170499157717562285497414389991880217624309652065642118273167262575395947172559346372386322614827426222086711558395999265211762526989175409881593486400834570851814722318142040704265090565323333984364578657967965192672923998753666172159825788602633636178274959942194037777536814262177387991945513972312740668983299898953867288228563786977496625199665835257761989393228453447356947949629521688914854925389047558288345260965240965428893945386466257449275563819644103169798330618520193793849400571563337205480685405758679996701213722394758214263065851322174088323829472876173936474678374319600015921888073478576172522118674904249773669292073110963697216089337086611567345853348332952546758516447107578486024636008344491148185876555542864551233142199263113325179706084365597043528564100879185007603610091594656706768836055717400767569050961367194013249356052401859991050621081635977264313806054670102935699710424251057817495310572559349844511269227803449135066375687477602831628296055324224269575345290288387684464291732827708883180870253398523381227499908123718925407264753678503048215918018861671089728692292011975998807038185433325364602110822992792930728717807998880991767417741089830608003263118164279882311715436386966170299993416161487868601804550555398691311518601038637532500455818604480407502411951843056745336836136745973744239885532851793089603738989151731958741344288178421250219169518755934443873961893145499999061075870490902608835176362247497578588583680374579311573398020999866221869499225959132764236194105921003280261498745665996888740679561673918595728886424734635858868644968223860069833526427990562831656139139425576490620651860216472630333629750756978706066068564981600927187092921531323682'
Note:
This can of course be generalized to calculating the square root of any number and of any order. This is an easy exercise for the reader.
Copyright © 2014, Konrad Burnik