为什么阶乘函数给我一个负数

发布于 2023-02-14 13:02:31

我制作了一个阶乘函数,当然可以让我计算阶乘,正如我们所知,一个阶乘永远不能 < 0.我的代码有时给了我负数......这是:

exception FactorialError of string;;
let rec factorial (n: int) : int = (
  if n < 0 then raise (FactorialError "The number has to be upper or equal then 0");
  if n == 0 then 1 else n * factorial(n-1);
);;

let value = ref (1);;
for i = 0 to 100 do
(
  value := factorial i;
  if !value = 0 then raise (FactorialError ("Factorial is no more possible after i = " ^ 
string_of_int i)) else print_string ("i: " ^ string_of_int i);
  print_string "\nValue: ";
  print_int !value;
  print_string "\n";
)
done;;

以下是其中一些的结果:

i: 0
Value: 1

i: 1
Value: 1

...

i: 20
Value : 2432902008176640000

i: 21
Value : -4249290049419214848 // <- Here is the problem

...这是问题,但不仅是 21 值,还有许多其他...

查看更多

1楼回答 2023-02-13

你有一个整数溢出.请注意,64bit 有符号整数必须在

[-9223372036854775808 .. 9223372036854775807]

范围。如果你走的话超过范围,你会得到不正确的价值:

2432902008176640000 * 21 == 51090942171709440000 > 9223372036854775807

如果你想计算精确的阶乘值,看看任意精度整数big_int

2楼回答 2023-02-06

下面是使用 Big_int 模块计算大阶乘值的代码:

$ cat bigfact.ml
open Big_int

let rec big_fact n =
    if n < 2 then unit_big_int
    else
        mult_big_int (big_int_of_int n) (big_fact (n - 1))

使用函数计算big_fact 100

$ ocaml nums.cma
OCaml version 4.14.0
Enter #help;; for help.

# #use "bigfact.ml";;
val big_fact : int -> Big_int.big_int = <fun>
# string_of_big_int (big_fact 100);;
- : string =
    "93326215443944152681699238856266700490
     71596826438162146859296389521759999322
     99156089414639761565182862536979208272
     23758251185210916864000000000000000000
     000000"

对于它的价值,我相信 Big_int 模块正在被更新的 Zarith 模块取代。

3楼回答 2023-02-12

发生的事情是 OCaml 的 int 是一个机器诠释;比例如选择的表示更有效,更接近硬件。一些流行的脚本语言,但有一个警告:机器整数是有限的(模)264或 232,机器字的大小.

OCaml 整数是further limited,因为它们是tagged,这对于垃圾收集语言的效率非常有用,因为这是避免大量分配的一种非常简单的方法。然而,这意味着它们是模 263.
int也是签,也就是说,可以用 2 表示的一半63numbers 保留为负数。

这一切都意味着当你使用int时,你的值只能在 [-4611686018427387904, 4611686018427387904) 范围内,从 -262最多但不包括 262.
幸运的是,您不必考虑所有这些,因为 OCaml 公开了 Int.(min_int, max_int),它很容易根据您的平台为您计算。如果您预计您的应用程序需要比 OCaml 提供的 int 更广泛的范围,那么您需要寻找更精细的东西。 21!恰好在这个范围之外。

“更精细的东西”将是 infinite precision 或内存支持的整数。来自ocaml/zarith 的Z module 是此数据结构的标准 OCaml 实现。

下面是我们将如何使用 Zarith,假设我们有一个依赖于 opamdune 的项目设置:

  1. opam install -y zarith 会把包带到本地,方便你在项目中使用。
  2. 添加(libraries zarith),或将zarith添加到现有(libraries ...)字段到使用zarith的模块旁边的dune文件。
  3. 你将能够像这样实现fac
    let rec zfac n =
     if Z.Compare.(n <= one) then Z.one else
        Z.(n * zfac (pred n))
    
    let fac n =
     if n < 0 then invalid_arg "fac n where n < 0" else zfac Z.(of_int n)
    

    现在,如果您使用例如Z.to_string在顶层检查fac 21的值,你会找到正确答案:51090942171709440000

    或者,如果您只是在 REPL 中尝试一些事情,并且您至少完成了上面的第 1 步,您可以输入 #require zarith;; 并以交互方式使用库。

请登录后再发布答案,点击登录