perl6:无法将65536位宽的bigint开箱到本机整数

时间:2019-02-05 06:36:16

标签: biginteger perl6

我尝试了Rosettacode中的一些示例,并遇到了所提供的Ackermann示例的问题:在“未修改”运行它时(我将utf-8变量名替换为latin-1变量名),得到了(类似,但现在可以复制):

Caught: groovy.lang.GroovyRuntimeException: Cannot read write-only property: credentialsProvider
groovy.lang.GroovyRuntimeException: Cannot read write-only property: credentialsProvider

删除第3行中的原始声明(通过注释掉):

$ perl6 t/ackermann.p6
65533
19729 digits starting with 20035299304068464649790723515602557504478254755697...
Cannot unbox 65536 bit wide bigint into native integer
  in sub A at t/ackermann.p6 line 3
  in sub A at t/ackermann.p6 line 11
  in sub A at t/ackermann.p6 line 3
  in block <unit> at t/ackermann.p6 line 17

出了什么问题?该程序不会分配太多内存。自然整数种类有限吗?

我将Ackermann function中的代码替换为m和the与n,以实现更好的终端交互以复制错误,并尝试注释掉原型声明。我也问丽兹;)

$ perl6 t/ackermann.p6
65533
19729 digits starting with 20035299304068464649790723515602557504478254755697...
Numeric overflow
  in sub A at t/ackermann.p6 line 8
  in sub A at t/ackermann.p6 line 11
  in block <unit> at t/ackermann.p6 line 17

2 个答案:

答案 0 :(得分:8)

请先阅读JJ的答案。轻而易举,并得出了这个答案,实际上是对它的阐述。

TL; DR A(4,3)是一个非常大数字,无法在此Universe中进行计算。但是乐堂会尝试的。这样一来,如果使用缓存版本,就会超出与内存分配和索引相关的合理限制,而如果不使用缓存,则会超出与数值计算有关的限制。


  

我尝试了Rosettacode的一些示例,并遇到了the provided Ackermann example

的问题

用一些强调的重点引用任务说明:

  

任意精度是首选(因为该功能增长如此快

P6的标准整数类型Intarbitrary precision。 P6解决方案使用它们来计算可能的最高级答案。只有当您尝试做不可能的事情时,它才会失败。

  

“未修改”运行时(我将utf-8变量名替换为拉丁1变量名)

替换变量名并不重要。

但是添加A(4,3)行将代码从现实中的可计算转换为现实中的不可计算。

您修改的示例只有一个解释性注释:

  

这是该版本的缓存版本... 使 A(4,2)成为可能

请注意,A(4,2)解决方案的长度接近20,000位。

如果您查看该页面上的其他解决方案,则大多数甚至都不会尝试访问A(4,2)。在Phix版本上有这样的评论:

  

已优化。仍然没有bignum库,因此ack(4,2),即power(2,65536)-3,显然是19729位及以上,超出了(CPU / FPU硬件)和此[代码]。 / p>

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

默认情况下,@变量对P6的the default concrete type使用abstract array type。这种默认的数组类型可在实现复杂性和出色性能之间取得平衡。

尽管计算A(4,2)的索引(mn)足够小,以至于计算可以完成而不会超出默认数组的索引限制。

此限制是“本地”整数(注意:不是 a "natural" integer)。 P6称为“本地”整数,由其运行的硬件支持固定宽度整数,通常为long long,而后者通常为64位。

64位宽的索引最多可以处理9,223,372,036,854,775,807个索引。

但是在尝试计算A(4,3)时,该算法会生成65536位(8192字节)宽的整数索引。这样的整数可能最大为2 65536 ,即20,032 decimal digit number。但是允许的最大索引是64位本机整数。因此,除非您注释掉使用数组的缓存行,否则对于A(4,3),程序最终将引发异常:

  

无法将65536位宽的bigint拆箱为本地整数

限于默认数组类型的分配和索引

正如已经说明的,没有足够大的数组可以帮助完全计算A(4,3)。另外,一个64位整数已经是一个很大的索引(9,223,372,036,854,775,807)。就是说,P6可以容纳更大的数组,因此我将在下面简要讨论,因为理论上的可能性可能会引起其他问题的兴趣。

但是在讨论更大的数组之前,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实现限制而引发的P6级异常。

即使为程序提供了大量内存,最后一条错误消息也会显示默认数组类型的索引限制。

如果有人想做更大的索引怎么办?

可以创建/使用其他支持稀疏数组等功能的@does Positional)数据类型。

而且,使用这种机制,可能有人可以编写一个数组实现,该数组支持比默认数组类型(可能是在底层平台指令之上分层逻辑)所支持的整数索引更大。

如果创建了这样的替代方案并将其命名为BigArray,则可以将高速缓存行替换为:

my @array is BigArray;
sub A(Int \, Int \) { @BigArray[][] //= {*} }

同样,这个 still 不足以存储用于完全计算A(4,3)的临时结果,但我的意思是展示自定义数组类型的使用。

Numeric overflow

注释掉缓存后,您将得到:

  

数字溢出

P6 / Rakudo执行任意精度运算。尽管有时将其称为无限精度,但实际上并不是(不可能)无限,而是“任意”,实际上,在计算中,“任意”对于“理智”。

从传统上讲,这意味着用尽内存来存储数字。但是在Rakudo的情况下,我认为有一种尝试,可以在完全用尽RAM之前从真正的Int切换到Num(浮点数)来保持正常。但是随后,计算A(4,3)甚至会溢出两次浮点数。

因此,虽然缓存很快就崩溃了,但无论如何,代码肯定会在以后崩溃,然后您将得到一个数值溢出,该溢出可能表现为内存不足错误或数值溢出错误。情况..

答案 1 :(得分:5)

数组下标使用native ints;这就是为什么在将大整数用作数组下标时在第3行出现错误的原因。您可能需要定义一个新的BigArray,该整数将Ints用作数组下标。

第二个问题出现在**运算符中:结果为Real,并且当低级运算返回Num时,它将引发异常。 https://github.com/rakudo/rakudo/blob/master/src/core/Int.pm6#L391-L401

因此创建BigArray可能无济于事。您还必须创建自己的**,该**始终可以与Int一起使用,但是您似乎已经达到了无限精度Ints的极限(不是那么无限)。