Julia code not finishing while Python code does

Question:

I am very new to Julia and was trying to implement/rewrite some of my previous Python code as practice. I was using the Project Euler problem 25 as practice

In Python I have

def fibonacci(N):
    """Returns the Nth Fibonacci Number"""
    F = [0, 1] 
    i = 0
    while i <= N-2:
        F_new = F[i] + F[i+1]
        F.append(F_new)
        i += 1
    return F[N]

N = 0 
x = 1000

while len(str(fibonacci(N))) <= x:
    if len(str(fibonacci(N))) == x:
        print(N)
        break     
    N = N + 1

Which runs and gives me the correct answer in about 6.5 seconds. When trying to do this in Julia below

function fib(N)
    F = [0, 1]
    global i = 1
    while i <= N-2
        F_new = F[i] + F[i+1]
        append!(F, F_new)
        global i += 1
    end
    return F[N]
end

N = 1
x = 1000

while length(string(fib(N))) <= x
    if length(string(fib(N))) == x
        print(N-1)
        break
    end
global N += 1
end

The code seems to run "forever". However in the Julia code only when x<= 20 will the code finish and produce the correct answer. In the Julia code when x>20 the program never ends.

I’m not sure where something could go wrong if it runs for all values below 21? Could somebody explain where the error is happening and why?

Asked By: Shawn

||

Answers:

Python integers are by default unbounded in size and will grow as needed. Julia on the other hand will default to a signed 64 bit integer if on a 64 bit system. (See docs) This begins to overflow when trying to calculate values above around 19 digits long, hence why this starts around x=20. In order to get the same behavior in Julia, you should use the BigInt type for any values or arguments which can get above this size.

Answered By: duckboycool

In addition to the earlier answer, note that you should avoid globals if looking at performance, so this is much faster than your global i and x code:

function fib(N)
    F = [big"0", big"1"]
    for i in 1:N-2
        F_new = F[i] + F[i+1]
        push!(F, F_new)
    end
    return F[N]
end

function test(x = 1000)
    N = 1
    while length(string(fib(N))) <= x
        if length(string(fib(N))) == x
            print(N-1)
            break
        end
        N += 1
    end
end

test()
Answered By: Bill

The main problem with your code is what @duckboycool has described. The second advice is to always write functions in Julia. Read the Julia performance tips page for a good start.

Note that you can make the function by @Bill 2X faster by removing the unnecessary if like this:

function test(x = 1000)
    N = 0
    while ndigits(fib(N)) < x
        N += 1
    end
    return N
end

But if you really want a 16000X faster Julia version, then you can do this:

function euler25()
    limit = big(10)^999
    a, b = big(1), big(1)
    N = 2
    while b <= limit
        a, b = b, a + b
        N += 1
    end
    return N
end

@btime euler25() = 4782
  377.700 μs (9573 allocations: 1.15 MiB)

This runs in 377 μs, because we avoid calculating fib(N) at every step from the beginning. And instead of comparing with the length of a string of the output at each iteration, we just compare with 10^999.

Answered By: AboAmmar

@AboAmmar shows probably the best "normal" way of writing this. But if you want something even a bit more optimized, you can use in-place BigInt calls. I’m not sure whether I would recommend this, but it’s nice to be aware of it.

using Base.GMP.MPZ: add!, set!
function euler25_(limit=big(10)^999)
    a, b = big(1), big(1)
    N = 2
    c = big(0)
    while b <= limit
        add!(c, a, b)
        set!(a, b)
        set!(b, c)
        N += 1
    end
    return N
end

This uses the special BigInt functions in the GMP.MPZ library, and writes values in-place, avoiding most of the allocations, and running 2.5x faster on my laptop.

Answered By: DNF
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.