G
Gregory Ewing
Chris said:In constant space, that will produce the sum of two infinite sequences
of digits.
It's not constant space, because the nines counter
can grow infinitely large.
Chris said:In constant space, that will produce the sum of two infinite sequences
of digits.
In constant space, that will produce the sum of two infinite sequences
of digits. (And it's constant time, too, except when it gets a stream
of nines. Adding three thirds together will produce an infinite loop
as it waits to see if there'll be anything that triggers an infinite
cascade of carries.) Now, if there's a way to do that for square
rooting a number, then the CF notation has a distinct benefit over the
decimal expansion used here. As far as I know, there's no simple way,
in constant space and/or time, to progressively yield more digits of a
number's square root, working in decimal.
def cf_sqrt(n):
"""Yield the terms of the square root of n as a continued fraction."""
m = 0
d = 1
a = a0 = floor_sqrt(n)
while True:
yield a
next_m = d * a - m
next_d = (n - next_m * next_m) // d
if next_d == 0:
break
next_a = (a0 + next_m) // next_d
m, d, a = next_m, next_d, next_a
It's not constant space, because the nines counter
can grow infinitely large.
Chris Angelico said:As far as I know, there's no simple way, in constant space and/or
time, to progressively yield more digits of a number's square root,
working in decimal.
I don't know why the constant space/time requirement is crucial. Anyway,
producing more digits simple: <URL: http://nrich.maths.org/5955>.
I believe producing the nth digit is O(n) in time and space.
The reason for striving for constant space/time is because the obvious
method (cut-and-try) is already O(n) for the nth digit, which means
it's quadratic on the number of digits desired. That gets pretty
nasty.
Oscar Benjamin said:To me the obvious method is Newton iteration which takes O(sqrt(N))
iterations to obtain N digits of precision. This brings the above
complexity below quadratic:
#!/usr/bin/env python
from decimal import Decimal as D, localcontext
def sqrt(y, prec=1000):
'''Solve x**2 = y'''
assert y > 0
eps = D(10) ** -(prec + 5)
x = D(y)
with localcontext() as ctx:
ctx.prec = prec + 10
while x ** 2 - y > x * eps:
x = (x + y/x) / 2
return x
print(sqrt(2))
I don't quite follow your reasoning here. By "cut-and-try" do you mean
bisection? If so it gives the first N decimal digits in N*log2(10)
iterations. However each iteration requires a multiply and when the
number of digits N becomes large the multiplication is worse than
linear. So the result is something like N**2 log(N)log(log(N)),
By "cut and try" I'm talking about the really REALLY simple algorithm
for calculating square roots. It's basically brute force.
epsilon = 0.0001
def sqrt(n):
guess1, guess2 = 1, n
while abs(guess1-guess2) > epsilon:
guess1 = n/guess2
guess2 = (guess1 + guess2)/2
return guess1
It's generally going to take roughly O(n*n) time to generate n digits,
give or take.
That's the baseline against which anything else can be
compared. There are plenty of better ways to calculate them.
At a quick glance, I believe x ** 2 is O(N²) and so the total complexity
should be O(N ** 2.5).
That's the exact same algorithm I showed! How on earth would you call
that brute force?
It does not take O(n*n) time. This is Newton iteration and for
well-behaved problems such as this it generates more than n digits
after n iterations. I modified my code to show the error (x**2 - y) at
each iteration:
$ python3.3 root.py
2
0.2
0.007
0.000006
5E-12
3E-24
8E-49
8E-98
8E-196
9E-392
1E-783
The number of correct digits doubles at each iteration so after n
iterations you have 2**n digits (I misstated this as n**2 before).
This means that it takes log(N) iterations to get N digits.
Such as?
It uses a lot of division. There are various refinements that can be
done that replace some of that division with multiplication, but I'd
have to go do some research to figure it out.
Let's compare two
versions. In the first, you set the precision (I'm talking in terms of
REXX's "NUMERIC DIGITS" statement
- anything beyond this many digits
will be rounded (and represented exponentially, if necessary); I'm not
sure if decimal.Decimal precision works this way) such that you get 10
digits.
Each iteration requires division by a 10-digit number, which
is an operation that takes a certain amount of time; and it's going to
take some number of iterations to get to the final answer.
Second version, you set the precision so you get 20 digits.
I have no idea what that is.
With the decimal module if you set the precision to 5 digits then it
basically represents the number in "standard form" with 5 digits .e.g:
1.2345 x 10**21.
If we're talking about 10-20 digits then the decimal module is
overkill: just use float. The speed up from hardware arithmetic will
massively out-weigh any other performance considerations.
Oscar Benjamin said:It does not take O(n*n) time. This is Newton iteration and for
well-behaved problems such as this it generates more than n digits
after n iterations. I modified my code to show the error (x**2 - y) at
each iteration:
$ python3.3 root.py
2
0.2
0.007
0.000006
5E-12
3E-24
8E-49
8E-98
8E-196
9E-392
1E-783
The number of correct digits doubles at each iteration so after n
iterations you have 2**n digits (I misstated this as n**2 before).
This means that it takes log(N) iterations to get N digits. See here
for more:
http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method
See also the section below that:
http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Digit-by-digit_calculation
Such as?
And no more than N bits (53 in a 64-bit float) in the numerator, and
the denominator between the limits of the exponent. (Unless it's
subnormal. That adds another set of small numbers.) It's a pretty
tight set of restrictions, and yet good enough for so many purposes.
But it's a far cry from "all real numbers". Even allowing for
continued fractions adds only some more; I don't think you can
represent surds that way.
ChrisA
The code for that looks like this:
def cf_sqrt(n):
"""Yield the terms of the square root of n as a continued fraction."""
m = 0
d = 1
a = a0 = floor_sqrt(n)
while True:
yield a
next_m = d * a - m
next_d = (n - next_m * next_m) // d
if next_d == 0:
break
next_a = (a0 + next_m) // next_d
m, d, a = next_m, next_d, next_a
def floor_sqrt(n):
"""Return the integer part of the square root of n."""
n = int(n)
if n == 0: return 0
lower = 2 ** int(math.log(n, 2) // 2)
upper = lower * 2
while upper - lower > 1:
mid = (upper + lower) // 2
if n < mid * mid:
upper = mid
else:
lower = mid
return lower
The floor_sqrt function is merely doing a simple binary search and
could probably be optimized, but then it's only called once during
initialization anyway. The meat of the loop, as you can see, is just
a constant amount of integer arithmetic. If it were desired to halt
once the continued fraction starts to repeat, that would just be a
matter of checking whether the triple (m, d, a) has been seen already.
Going back to your example of adding generated digits though, I don't
know how to add two continued fractions together without evaluating
them.
How "digital" our idealized computers are is a matter for a debate.
However, iterating over the continuum is provably "possible:"
http://en.wikipedia.org/wiki/Transfinite_induction
My assumption was you could execute ℵ₠statements per second. That
doesn't guarantee a finite finish time but would make it possible. That
is because
ℵ₠* ℵ₠= ℵ₠= ℵ₠* 1
This computer is definitely more powerful than a Turing machine, which
only has ℵ₀ bytes of RAM and thus can't even store an arbitrary real
value in memory.
Adding cf's adds all computable numbers in infinite precision. However
that is not even a drop in the ocean, as the computable numbers have
measure zero.
On the other hand, it's not really clear that the non-computable numbers
are useful or necessary for anything. They exist as mathematical
abstractions, but they'll never be the result of any calculation or
measurement that anyone might do.
Want to reply to this thread or ask your own question?
You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.