请先阅读 JJ 的回答。它很轻松,导致了这个答案,实际上是对它的阐述。
TL;DR A(4,3) 是一个非常大的数字,在这个宇宙中无法计算。但是 raku(do) 会尝试。如果您使用缓存版本,您将超出与内存分配和索引相关的合理限制,如果不使用,则会超出与数字计算相关的限制。
我尝试了一些来自 Rosettacode 的示例,但遇到了 the provided Ackermann example 的问题
引用任务描述并添加一些强调:
任意精度是首选(因为函数增长如此之快)
raku 的标准整数类型Int 是arbitrary precision。 raku 解决方案使用它们来计算可能的最高级答案。只有当你让它尝试做不可能的事情时它才会失败。
当“未修改”运行时(我用 latin-1 替换了 utf-8 变量名)
替换变量名并不是一个重大变化。
但添加 A(4,3) 行会将代码从现实中可计算变为现实中不可计算。
您修改的示例只有一个解释性注释:
这是一个缓存版本... 使 A(4,2) 成为可能
请注意,A(4,2) 解决方案的长度接近 20,000 位。
如果您查看该页面上的其他解决方案,大多数人甚至都不会尝试联系A(4,2)。 Phix 版上有这样的 cmets:
优化。仍然没有 bignum 库,所以 ack(4,2),即 power(2,65536)-3,显然是 19729 位,以及任何以上,都超出了(CPU/FPU 硬件)和这个 [code]。
A(4,2) 的解决方案是最先进的。
A(4,3) 在实践中是不可计算的
引用Academic Kids: Ackermann function:
即使对于较小的输入(例如 4,3),阿克曼函数的值也会变得如此之大,以至于无法对其进行计算,实际上它们的十进制展开式甚至无法存储在整个物理宇宙中。
所以计算A(4,3).say 是不可能的(在这个宇宙中)。
它必须不可避免地导致任意精度整数运算的溢出。这只是时间和方式的问题。
Cannot unbox 65536 bit wide bigint into native integer
第一条错误信息提到了这行代码:
proto A(Int \m, Int \n) { (state @)[m][n] //= {*} }
state @ 是 anonymous state array variable。
默认情况下,@ 变量使用 the default concrete type 表示 raku 的 abstract array type。这种默认数组类型提供了实现复杂性和良好性能之间的平衡。
在计算 A(4,2) 时,索引(m 和 n)仍然足够小,以至于计算完成时不会超出默认数组的索引限制。
这个限制是一个“本地”整数(注意:不是"natural" integer)。 “本机”整数是 raku 所称的由其运行的硬件支持的固定宽度整数,通常是 long long,通常是 64 位。
64 位宽的索引可以处理高达9,223,372,036,854,775,807 的索引。
但在尝试计算 A(4,3) 时,该算法会生成一个 65536 位(8192 字节)宽的整数索引。这样的整数可以大到 265536,即20,032 decimal digit number。但允许的最大索引是 64 位本机整数。所以除非你注释掉使用数组的缓存行,否则对于A(4,3),程序最终会抛出异常:
无法将 65536 位宽的 bigint 拆箱为原生整数
默认数组类型的分配和索引限制
正如已经解释的那样,没有足够大的数组来帮助完全计算A(4,3)。此外,64 位整数已经是一个相当大的索引 (9,223,372,036,854,775,807)。
也就是说,raku 可以适应其他数组实现,例如 Array::Sparse,所以我将在下面简要讨论这一点,因为这些可能性可能对其他问题感兴趣。
但在讨论更大的数组之前,running the code below on tio.run 显示了该平台上默认数组类型的实际限制:
my @array;
@array[2**29]++; # works
@array[2**30]++; # could not allocate 8589967360 bytes
@array[2**60]++; # Unable to allocate ... 1152921504606846977 elements
@array[2**63]++; # Cannot unbox 64 bit wide bigint into native integer
(注释掉错误行以查看以后/更大的错误。)
“无法分配 8589967360 字节”错误是 MoarVM 恐慌。这是 tio.run 拒绝内存分配请求的结果。
我认为“无法分配...元素”错误是由于超出某些内部 Rakudo 实现限制而引发的 raku 级别异常。
最后一条错误消息显示默认数组类型的索引限制,即使有大量内存可供程序使用。
如果有人想做更大的索引怎么办?
可以创建/使用支持稀疏数组等的其他 @ (does Positional) 数据类型。
而且,使用这种机制,有人可能会编写一个数组实现,它支持比默认数组类型支持的更大的整数索引(可能是通过在底层平台指令之上分层逻辑;也许Array::Sparse I上面的链接确实)。
如果这样的替代方法被称为BigArray,那么缓存行可以替换为:
my @array is BigArray;
proto A(Int \?, Int \?) { @array[?][?] //= {*} }
同样,这仍然不足以存储临时结果以完全计算A(4,3),但我的意思是展示自定义数组类型的使用。
Numeric overflow
当你注释掉你得到的缓存时:
数值溢出
Raku/Rakudo 做任意精度的算术。虽然这有时被称为无限精度,但它显然不是实际上无限的,而是“任意”,在这种情况下,对于“理智”的某些定义,这也意味着“理智”。 p>
这通常意味着存储数字的内存不足。但在 Rakudo 的情况下,我认为在完全耗尽 RAM 之前,尝试通过从真正巨大的 Int 切换到 Num(浮点数)来保持理智。但随后计算 A(4,3) 最终会溢出甚至是双浮点数。
因此,虽然缓存很快就会爆炸,但无论如何代码肯定会在以后爆炸,然后你会得到一个数字溢出,它要么表现为内存不足错误,要么表现为数字溢出错误,就像在这个案例。