0

I've run across a little problem when trying to solve a Project Euler problem in Julia. I've basically written a recursive function which produces fractions with increasingly large numerators and denominators. I don't want to post the code for obvious reasons, but the last few fractions are as follows:

1180872205318713601//835002744095575440
2850877693509864481//2015874949414289041
6882627592338442563//4866752642924153522

At that point I get an OverflowError(), presumably because the numerator and/or denominator now exceeds 19 digits. Is there a way of handling 'Big' fractions in Julia (i.e. those with BigInt-type numerators and denominators)?

Addendum:

OK, I've simplified the code and disguised it a bit. If anyone wants to wade through 650 Project Euler problems to try to work out which question it is, good luck to them – there will probably be around 200 better solutions!

function series(limit::Int64, i::Int64=1, n::Rational{Int64}=1//1)
    while i <= limit
        n = 1 + 1//(1 + 2n)
        println(n)
        return series(limit, i + 1, n)
    end
end

series(50)

If I run the above function with, say, 20 as the argument it runs fine. With 50 I get the OverflowError().

Mark Birtwistle
  • 362
  • 2
  • 10
  • Does your recursive function have an exit condition? If not, the overflow would be due to the function continuing forever... – Trevor Clarke May 14 '18 at 16:09
  • It would be nice if Julia threw an overflow error for this. It doesn't. – Bill Bell May 14 '18 at 16:29
  • 1
    Hard to say without more information about the code itself. If you're explicitly using the `big` function or `BigInt`, then you shouldn't hit overflow errors. @BillBell: since overflow is so common in unreduced fractions, the Rational code uses checked arithmetic. – mbauman May 14 '18 at 16:31
  • @MattB.: Thanks, hadn't tried that. – Bill Bell May 14 '18 at 16:32
  • @TrevorClarke, my recursive function does have an exit condition (actually a limit on the number of iterations). When I reduce the limit, the function runs fine, but the size of the numerator and denominator doesn't approach 19 digits in that case. – Mark Birtwistle May 14 '18 at 16:50
  • @MattB, I'm only using a Rational type, I'm not explicitly using Big anything. Is there a BigRational type? I guess that's what I'm after. I don't know what 'checked arithmetic' is, I'm neither a mathematician nor a programmer! – Mark Birtwistle May 14 '18 at 16:50
  • @MarkBirtwistle I would think that your limit is too high then, the recursive function is calling itself too many times before hitting the limit. I suggest you try lowering the limit to something tiny (say 2 digits) and see if it works – Trevor Clarke May 14 '18 at 17:08
  • @TrevorClarke, yeah it works fine with a lower limit. I've now posted a simplified version of the code, so you should be able to see my problem. – Mark Birtwistle May 14 '18 at 17:18
  • 1
    As a side remark, you probably don't need to worry so much about posting Project Euler solutions - there are many many blogs and github projects and other places with solutions already. If someone decides to do the pointless task of copy-pasting solutions for a checkmark, they have several existing sources for such code anyway. – Sundar R May 14 '18 at 19:40

1 Answers1

4

Julia defaults to using machine integers. For more information on this see the FAQ: Why does Julia use native machine integer arithmetic?.

In short: the most efficient integer operations on any modern CPU involves computing on a fixed number of bits. On your machine, that's 64 bits.

julia> 9223372036854775805 + 1
9223372036854775806

julia> 9223372036854775805 + 2
9223372036854775807

julia> 9223372036854775805 + 3
-9223372036854775808

Whoa! What just happened!? That's definitely wrong! It's more obvious if you look at how these numbers are represented in binary:

julia> bitstring(9223372036854775805 + 1)
"0111111111111111111111111111111111111111111111111111111111111110"

julia> bitstring(9223372036854775805 + 2)
"0111111111111111111111111111111111111111111111111111111111111111"

julia> bitstring(9223372036854775805 + 3)
"1000000000000000000000000000000000000000000000000000000000000000"

So you can see that those 63 bits "ran out of space" and rolled over — the 64th bit there is called the "sign bit" and signals a negative number.

There are two potential solutions when you see overflow like this: you can use "checked arithmetic" — like the rational code does — that ensures you don't silently have this problem:

julia> Base.Checked.checked_add(9223372036854775805, 3)
ERROR: OverflowError: 9223372036854775805 + 3 overflowed for type Int64

Or you can use a bigger integer type — like the unbounded BigInt:

julia> big(9223372036854775805) + 3
9223372036854775808

So an easy fix here is to remove your type annotations and dynamically choose your integer types based upon limit:

function series(limit, i=one(limit), n=one(limit)//one(limit))
    while i <= limit
        n = 1 + 1//(1 + 2n)
        println(n)
        return series(limit, i + 1, n)
    end
end

julia> series(big(50))
#…
1186364911176312505629042874//926285732032534439103474303
4225301286417693889465034354//3299015554385159450361560051
mbauman
  • 30,958
  • 4
  • 88
  • 123
  • Thanks @MattB for the very detailed explanation – some of which I knew, and some of which I didn't. If I'd used my brain I would have realised that I could have used Rational{BigInt} rather than Rational{Int64}. I've never used the literal one() – or zero() – functions, so that adds to my knowledge base. – Mark Birtwistle May 14 '18 at 18:09