简体   繁体   English

f#中的int溢出

[英]Overflow of an int in f#

I am working on some homework and we are supposed to be making a combination function in F#. 我正在做一些作业,我们应该在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. (假设20)我知道我可以使用int64或float,但这会更改代码上的所有输入。 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;; 在代码上,当我组合20 5 ;; it gives me 2147. Which is clearly the wrong answer. 它给了我2147。这显然是错误的答案。 I looked at the factorial function and when I put 20 in there it gave me a big negative number. 我看了阶乘函数,当我在其中放20时,它给了我很大的负数。 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. 首先,如果要避免意外,可以打开文件顶部的Checked模块。 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 . 正如Fyodor在评论中指出的那样,您不能将int 20的阶乘设置为int并且需要int64 However, your combo function then performs division which will make the result of combo 20 5 small enough to fit into int . 但是,然后,您的combo函数执行除法运算,这将使combo 20 5的结果足够小以适合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: 一种选择是将Fact更改为使用int64 ,但将combo用作接受并返回整数的函数-您需要在调用Fact之前将它们转换为int64 ,然后在执行除法操作后将其转换为int

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. 现在您可以调用combo 20 5 ,结果为15504

EDIT: As noted by @pswg in the other answer, int64 is also quite limited and so you'll need BigInteger for larger factorials. 编辑:正如在另一个答案中@pswg所指出的那样, int64也非常有限,因此对于较大的阶乘,您将需要BigInteger However, the same method should work for you with BigInteger . 但是,使用BigInteger可以使用相同的方法。 You can keep the combo function as a function that returns int by converting back from BigInteger to int . 你可以保持combo功能作为一个返回函数int通过转换从后面BigIntegerint

You simply won't be able to do that with an 32-bit integer ( int ). 您根本无法使用32位整数( int )来做到这一点。 A 64-bit integer will get you up to 20! 64位整数最多可以使您达到20! , but will fail at 21! ,但将在21!失败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#). 要进一步发展,您将需要使用System.Numerics.BigInteger (在F#中为bigint缩写)。

The parameter can probably stay as an int to be reasonable, but you need to return a bigint : 该参数可以合理地保留为int ,但是您需要返回一个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). 现在,在您的Combo函数中,您将需要在内部对所有数学使用这些bigint (很幸运,在这种情况下,只需要整数除法)。

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: 如果您真的想让Combo返回一个int ,则可以在此处进行转换:

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. 编辑 :通过重新考虑Combo的实现,您可以从中获得一些重大的性能改进。 See this question on Math.SE for the basis of this implementation: 有关实现的基础,请参见Math.SE上的以下问题:

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: 快速测试平台显示,这是显著比快Fact之上为基础的版本:

Function             Avg. Time (ms)
Combo 99 5            30.12570 
ComboFast 99 5         0.72364

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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