Reputation: 95
I am working on some homework and we are supposed to be making a combination function in F#. I have got the factorial function down, but it seems to overflow once I get a big number to use factorial on. (Let's say 20) I understand I can use an int64 or a float, but that would change all the inputs on the code. What data type should I use?
let rec Fact (a:int)=
if (a = 0) then 1 else a*Fact(a-1);;
let combo (n:int) (k:int)=
if (n = 0) then 0 else (Fact n)/((Fact k)*(Fact (n-k)));;
On the code right now, when I do combo 20 5;; it gives me 2147. Which is clearly the wrong answer. I looked at the factorial function and when I put 20 in there it gave me a big negative number. Any help would be much appreciated. Thanks in advance.
Upvotes: 4
Views: 1528
Reputation: 243096
First of all, if you want to avoid surprises, you can open the Checked
module at the top of your file. This will redefine the numerical operators so that they perform overflow checks - and you'll get an exception rather than unexpected number:
open Microsoft.FSharp.Core.Operators.Checked
As Fyodor points out in the comment, you cannot fit factorial of 20 in int
and you need int64
. However, your combo
function then performs division which will make the result of combo 20 5
small enough to fit into int
.
One option is to change Fact
to use int64
, but keep combo
as a function that takes and returns integers - you'll need to convert them to int64
before calling Fact
and then back to int
after performing the division:
let rec Fact (a:int64) =
if (a = 0L) then 1L else a * Fact(a-1L)
let combo (n:int) (k:int) =
if (n = 0) then 0 else int (Fact (int64 n) / (Fact (int64 k) * Fact (int64 (n-k))))
Now you can call combo 20 5
and you'll get 15504
as the result.
EDIT: As noted by @pswg in the other answer, int64
is also quite limited and so you'll need BigInteger
for larger factorials. However, the same method should work for you with BigInteger
. You can keep the combo
function as a function that returns int
by converting back from BigInteger
to int
.
Upvotes: 6
Reputation: 149040
You simply won't be able to do that with an 32-bit integer (int
). A 64-bit integer will get you up to 20!
, but will fail at 21!
. The numbers just get too big, too quickly. To go any further than that you'll need to use System.Numerics.BigInteger
(abbreviated bigint
in F#).
The parameter can probably stay as an int
to be reasonable, but you need to return a bigint
:
let rec Fact (n : int) =
if n = 0 then bigint.One else (bigint n) * Fact (n - 1)
Or to be a little more idiomatic:
let rec Fact = function | 0 -> bigint.One | n -> (bigint n) * Fact (n - 1)
And now, in your Combo
function, you'll need to use these bigint
's internally for all math (thankfully the integer division is all you need in this case).
let Combo (n : int) (k : int) =
if n = 0 then bigint.Zero else (Fact n) / ((Fact k) * (Fact (n - k)))
If you really wanted to make Combo
return an int
, you can do that conversion here:
let Combo (n : int) (k : int) =
if n = 0 then 0 else (Fact n) / ((Fact k) * (Fact (n - k))) |> int
Examples:
Combo 20 5 // --> 15504
Combo 99 5 // --> 71523144 (would break if you used int64)
Edit: By rethinking your implementation of Combo
you can get some big performance improvements out of this. See this question on Math.SE for the basis of this implementation:
let ComboFast (n : int) (k : int) =
let rec Combo_r (n : int) = function
| 0 -> bigint.One
| k -> (bigint n) * (Combo_r (n - 1) (k - 1)) / (bigint k)
Combo_r n (if (2 * k) > n then n - k else k)
A quick benchmark showed this to be significantly faster than the Fact
-based version above:
Function Avg. Time (ms)
Combo 99 5 30.12570
ComboFast 99 5 0.72364
Upvotes: 3