简体   繁体   中英

Overflow of an int in f#

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.

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 .

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM