Odd period square roots (Euler Problem 64)

Back to overview.

https://projecteuler.net/problem=64

The first ten continued fraction representations of (irrational) square roots are:

√2=[1;(2)], period=1

√3=[1;(1,2)], period=2

√5=[2;(4)], period=1

√6=[2;(2,4)], period=2

√7=[2;(1,1,1,4)], period=4

√8=[2;(1,4)], period=2

√10=[3;(6)], period=1

√11=[3;(3,6)], period=2

√12= [3;(2,6)], period=2

√13=[3;(1,1,1,1,6)], period=5

Exactly four continued fractions, for N ≤ 13, have an odd period.

How many continued fractions for N ≤ 10000 have an odd period?

In [1]:
import math

def get_floor_sqrt(n):
    return math.floor(math.sqrt(n))

assert(get_floor_sqrt(5) == 2)
assert(get_floor_sqrt(23) == 4)
In [2]:
def next_expansion(current_a, current_nominator, current_denominator, original_number):
    # Less typing
    cn = current_nominator
    cd = current_denominator

    # Step 1: Multiply the fraction so that we can use the third binomial formula.
    # Make sure we can reduce the nominator.
    assert((original_number - cd * cd) % cn == 0)
    # The new nominator is the denominator since we multiply with (x + cd) and then
    # reduce the previous nominator.
    # The new denominator is calculated by applying the third binomial formula and
    # then by divided by the previous nominator.
    cn, cd = cd, (original_number - cd * cd) // cn
    
    # Step 2: Calculate the next a by finding the next floor square root.
    next_a = math.floor((math.sqrt(original_number) + cn) // cd)
    
    # Step 3: Remove next a from the fraction by substracting it.
    cn = cn - next_a * cd
    cn *= -1
    
    return next_a, cn, cd

next_expansion(1, 7, 3, 23)
Out[2]:
(3, 3, 2)
In [3]:
def get_continued_fraction_sequence(n):
    
    # If number is a square number we return it.
    floor_sqrt = get_floor_sqrt(n)
    if n == floor_sqrt * floor_sqrt:
        return ((floor_sqrt), [])
    
    # Otherwise, we calculate the next expansion till we
    # encounter a step a second time.    
    a = floor_sqrt
    cn = a
    cd = 1
    sequence = []
    previous_steps = []
    
    while not (a, cn, cd) in previous_steps :
        #print("a: {} cn: {} cd: {}".format(a, cn, cd))
        previous_steps.append((a, cn, cd))
        a, cn, cd = next_expansion(a, cd, cn, n)
        sequence.append(a)
    sequence.pop()
    return ((floor_sqrt), sequence)

assert(get_continued_fraction_sequence(1) == ((1), []))
assert(get_continued_fraction_sequence(4) == ((2), []))
assert(get_continued_fraction_sequence(25) == ((5), []))
assert(get_continued_fraction_sequence(2) == ((1), [2]))
assert(get_continued_fraction_sequence(3) == ((1), [1,2]))
assert(get_continued_fraction_sequence(5) == ((2), [4]))
assert(get_continued_fraction_sequence(13) == ((3), [1,1,1,1,6]))
In [4]:
def get_period(n):
    _, sequence = get_continued_fraction_sequence(n)
    return len(sequence)

assert(get_period(23) == 4)
In [5]:
s = len([n for n in range(1, 10001) if get_period(n) % 2 != 0])
print(s)
assert(s == 1322)
1322
In [ ]: