12

我在 Python 中定义了一个阶乘函数,如下所示:

def fact(n):
    if n == 1:
        return n
    else:
        return n * fact(n-1)

print(fact(100))

在 Julia 中如下:

function fact(n)
    if n == 1
        n
    else
        n * fact(n-1)
    end
end

println(fact(100))

python 程序返回一个非常大的数字来评估 100(如预期的那样)。Julia 返回 0。对于较小的数字(如 10),它们都可以工作。

我有两个问题:

  1. 为什么 Python 可以处理这个问题而 Julia 不行。
  2. 为什么 Julia 不抛出错误而只打印 0 呢?
4

6 回答 6

20

Julia 有单独的固定大小整数类型,以及 BigInt 类型。默认类型是Int64,当然是 64 位。

自从100!大约需要 526 位,它显然会溢出Int64.

你可以通过做fact(BigInt(100))(假设你已经做require了)来解决这个问题,或者当然你可以在fact函数中进行转换。


Python曾经是一样的,曾几何时。它有单独的 types int,根据您的机器是 16 位或 32 位或 64 位,并且long是任意长度。如果你在 Python 1.5 上运行你的程序,它要么像 Julia 一样回绕,要么引发异常。解决方案是调用,或在函数内部进行fact(100L)转换。longfact

然而,在 2.x 系列的某个时刻,Python 将这两种类型绑定在一起,因此任何int自动溢出的类型都变成了long. 然后,在 3.0 中,它完全合并了这两种类型,因此不再分离long


那么,为什么 Julia 只是溢出而不是引发错误呢?

常见问题解答实际上解释了为什么 Julia 使用本机机器整数算术。其中包括溢出时的环绕行为。


人们通常所说的“本机机器算术”是指“C 在几乎所有 2s 补码机器上所做的事情”。尤其是像 Julia 和 Python 这样的语言,它们最初是建立在 C 之上的,并且非常接近金属。在 Julia 的情况下,这不仅仅是一个“默认”,而是一个有意的选择。

在 C 语言中(至少当时是这样),如果你溢出一个有符号整数类型会发生什么,实际上取决于实现int64……但在几乎任何本机使用 2 补码算法的平台上(这几乎是你将要使用的任何平台)看到今天),完全相同的事情发生了:它只是截断了前 64 位以上的所有内容,这意味着你从正到负环绕。事实上,无符号整数类型需要在 C 中以这种方式工作。(同时,C 以这种方式工作,因为这是大多数 CPU 的工作方式。)

在 C 语言中(大多数 CPU 的机器语言不同),没有办法在事后检测到您是否发生了溢出。所以,如果你想提高一个OverflowError,你必须编写一些逻辑来检测乘法是否会溢出。你必须在每一次乘法上运行这个逻辑。您可以通过编写内联汇编代码来针对某些平台对此进行优化。int64或者您可以转换为更大的类型,但是(a)这往往会使您的代码变慢,并且(b)如果您已经在使用最大的类型(今天在许多平台上),它就不起作用。

在 Python 中,将每次乘法的速度降低 4 倍(通常更慢,但也可能如此之高)没什么大不了的,因为与无论如何乘法相比,Python 花费更多时间获取字节码和拆箱整数对象。但 Julia 的本意是比这更快。

正如 John Myles White 在Computers are Machines中解释的那样:

在许多方面,Julia 将自己与其他新语言区分开来,因为它试图恢复从 C 语言到 Python 等语言的过渡过程中失去的一些功能。但这种转变伴随着大量的学习曲线。


但是还有另一个原因:溢出有符号算术在许多情况下实际上是有用的。几乎没有溢出无符号算术那么多(这就是为什么 C 在第一个 ANSI 规范之前就已经定义了无符号算术以这种方式工作),但是有一些用例。

而且,即使您可能需要比翻转更频繁的类型转换,手动进行类型转换也比翻转要容易得多。如果你曾经在 Python 中做过,那么选择操作数%并正确使用符号肯定很容易出错。铸造到BigInt很难搞砸。


最后,在像 Python 和 Julia 这样的强类型语言中,类型稳定性很重要。Python 3 存在的原因之一是旧str类型神奇地转换为unicode导致问题。您的int类型神奇地转换为long导致问题的情况要少得多,但它可能会发生(例如,当您从线路中或通过 C API 获取值时,并希望以相同的格式写出结果) . intPython 的开发团队在进行/统一时对此进行了争论long,引用了“实用性胜过纯洁性”和 Zen 的各种其他内容,并最终确定旧行为比新行为引起的问题更多。朱莉娅的设计做出了相反的决定。

于 2014-01-15T02:18:10.073 回答
18

没有人回答为什么 Julia 的结果是 0。

Julia 不检查整数乘法是否溢出,因此 64 位整数的乘法是在模 2^63 下执行的。请参阅此常见问题解答条目

当您写出阶乘的乘法时,您会得到

1*2*3*4*5*6*7*8*9*10*11*12*13*14*15*16*17*18*19*20*21*22*23*24*25*26*27*28*29*30*31*32*33*34*35*36*37*38*39*40*41*42*43*44*45*46*47*48*49*50*51*52*53*54*55*56*57*58*59*60*61*62*63*64*65*66*67*68*69*70*71*72*73*74*75*76*77*78*79*80*81*82*83*84*85*86*87*88*89*90*91*92*93*94*95*96*97*98*99*100

这也可以写成素因数

2^97 * 3^48 * 5^24 * 7^16 * 11^9 * 13^7 * 17^5 * 19^5 * 23^4 * 29^3 * 31^3 * 37^2 * 41^2 * 43^2 * 47^2 * 53^1 * 59^1 * 61^1 * 67^1 * 71^1 * 73^1 * 79^1 * 83^1 * 89^1 * 97^1

如果你看一下你的指数,2你会得到97. 模运算使您可以在计算的任何步骤执行 mod 功能,并且不会影响结果。2^97 mod 2^63 == 0与链的其余部分相乘也是 0。

更新: 我当然懒得在纸上做这个计算。

d = Dict{Int,Int}()
for i=1:100
   for (k,v) in factor(i)
       d[k] = get(d,k,0) + v
   end
end
for k in sort(collect(keys(d)))
    print("$k^$(d[k])*")
end

Julia 在其标准库中有一个非常方便的 factor() 函数。

于 2014-01-15T21:05:50.340 回答
10

我想这个问题的答案是使用BigInt

function fact(n::BigInt)                                                                                                                                      
    if n == BigInt(1)                                                                                                                                         
        n                                                                                                                                             
    else                                                                                                                                              
        n * fact(n-BigInt(1))                                                                                                                             
    end                                                                                                                                               
end                                                                                                                                                   

println(fact(BigInt(100))) 

这给出了结果:

93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

测试: http: //forio.com/julia/repl/

正如在其他一些答案中所述,Python 为您隐式地将超过最大大小的 int(s) 转换为 bigint(s),因此您可以获得预期的结果,而不是默默地失败。

另一方面,朱莉娅似乎对此更加明确,并且倾向于表现而不是“预期行为”。Julia 是一种具有可选类型注释和推理的动态语言。

于 2014-01-15T02:21:29.823 回答
2

Python 自动使用可以容纳任意大数字的 BigInt。在 Julia,你必须自己做。我认为它是这样纠正的

function fact(n::BigInt)                                                                                                                                      
    if n == 1                                                                                                                                         
        n                                                                                                                                             
    else                                                                                                                                              
        n * fact(n-1)                                                                                                                                 
    end                                                                                                                                               
end                                                                                                                                                   

println(fact(BigInt(100))) 
于 2014-01-15T02:19:44.660 回答
0

Julia 速度快的原因之一是它们避免了会影响性能的功能。这是其中之一。在 Python 中,解释器不断检查它是否应该自动切换到 BigInt 库。这种不断的检查是有代价的。

这是一个可以满足您要求的功能:

function fact(n)
    if n == 0
        1
    else
        big(n) * fact(n-1)
    end
end

println( fact(100) )
println( fact(0) )

我冒昧地纠正了您程序中的错误:定义了零阶乘,并且是 1。顺便说一句,您可以这样编写函数:

function !(n)
    if n == 0
        1
    else
        big(n) * !(n-1)
    end
end

println( !(100) )
println( !(0) )

我不会亲自这样做,因为“foo!” 函数通常用于修改参数的函数。但我想提供选择。最后,我无法抗拒提供单线替代方案:

fact(n) = n == 0 ? 1 : big(n) * !(n-1)

println( fact(100) )
println( fact(0) )
于 2014-01-18T04:32:36.807 回答
0

顺便说一句,我认为在 Julia 中这样做的惯用方法是利用类型系统为不同类型编译不同的版本:

fact(n) = n <= zero(n) ? one(n) : n*fact(n-one(n)) 
# one(n) gives you a one, as it were, of the same type as n

然后,根据输入的类型编译和调用该函数的不同版本,用户必须决定使用哪种类型,从而决定调用哪个版本的函数:

julia> fact(10)
3628800

julia> fact(100)
0

julia> fact(BigInt(100))
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

当我们查看 Int64 与 BigInt fact() 的 (LLVM) 编译版本时,可以看到 BigInt 与机器 (Int64) 算法的开销,由 @abarnert 进行了很好的描述:

julia> code_llvm(fact,(Int64,))

define i64 @"julia_fact;23421"(i64) {
top:
  %1 = icmp sgt i64 %0, 0, !dbg !10800
  br i1 %1, label %L, label %if, !dbg !10800

if:                                               ; preds = %top
  ret i64 1, !dbg !10800

L:                                                ; preds = %top
  %2 = add i64 %0, -1, !dbg !10800
  %3 = call i64 @"julia_fact;23398"(i64 %2), !dbg !10800
  %4 = mul i64 %3, %0, !dbg !10800
  ret i64 %4, !dbg !10800
}



julia> code_llvm(fact,(BigInt,))

define %jl_value_t* @"julia_fact;23422"(%jl_value_t*, %jl_value_t**, i32) {
top:
  %3 = alloca [6 x %jl_value_t*], align 8
  %.sub = getelementptr inbounds [6 x %jl_value_t*]* %3, i64 0, i64 0
  %4 = getelementptr [6 x %jl_value_t*]* %3, i64 0, i64 2, !dbg !10803
  store %jl_value_t* inttoptr (i64 8 to %jl_value_t*), %jl_value_t** %.sub, align 8
  %5 = load %jl_value_t*** @jl_pgcstack, align 8, !dbg !10803
  %6 = getelementptr [6 x %jl_value_t*]* %3, i64 0, i64 1, !dbg !10803
  %.c = bitcast %jl_value_t** %5 to %jl_value_t*, !dbg !10803
  store %jl_value_t* %.c, %jl_value_t** %6, align 8, !dbg !10803
  store %jl_value_t** %.sub, %jl_value_t*** @jl_pgcstack, align 8, !dbg !10803
  store %jl_value_t* null, %jl_value_t** %4, align 8, !dbg !10803
  %7 = getelementptr [6 x %jl_value_t*]* %3, i64 0, i64 3
  store %jl_value_t* null, %jl_value_t** %7, align 8
  %8 = getelementptr [6 x %jl_value_t*]* %3, i64 0, i64 4
  store %jl_value_t* null, %jl_value_t** %8, align 8
  %9 = getelementptr [6 x %jl_value_t*]* %3, i64 0, i64 5
  store %jl_value_t* null, %jl_value_t** %9, align 8
  %10 = load %jl_value_t** %1, align 8, !dbg !10803
  %11 = call %jl_value_t* @julia_BigInt2(i64 0), !dbg !10804
  store %jl_value_t* %11, %jl_value_t** %4, align 8, !dbg !10804
  %12 = getelementptr inbounds %jl_value_t* %10, i64 1, i32 0, !dbg !10804
  %13 = getelementptr inbounds %jl_value_t* %11, i64 1, i32 0, !dbg !10804
  %14 = call i32 inttoptr (i64 4535902144 to i32 (%jl_value_t**, %jl_value_t**)*)(%jl_value_t** %12, %jl_value_t** %13), !dbg !10804
  %15 = icmp sgt i32 %14, 0, !dbg !10804
  br i1 %15, label %L, label %if, !dbg !10804

if:                                               ; preds = %top
  %16 = call %jl_value_t* @julia_BigInt2(i64 1), !dbg !10804
  %17 = load %jl_value_t** %6, align 8, !dbg !10804
  %18 = getelementptr inbounds %jl_value_t* %17, i64 0, i32 0, !dbg !10804
  store %jl_value_t** %18, %jl_value_t*** @jl_pgcstack, align 8, !dbg !10804
  ret %jl_value_t* %16, !dbg !10804

L:                                                ; preds = %top
  store %jl_value_t* %10, %jl_value_t** %7, align 8, !dbg !10804
  store %jl_value_t* %10, %jl_value_t** %8, align 8, !dbg !10804
  %19 = call %jl_value_t* @julia_BigInt2(i64 1), !dbg !10804
  store %jl_value_t* %19, %jl_value_t** %9, align 8, !dbg !10804
  %20 = call %jl_value_t* @"julia_-;23402"(%jl_value_t* inttoptr (i64 140544121125120 to %jl_value_t*), %jl_value_t** %8, i32 2), !dbg !10804
  store %jl_value_t* %20, %jl_value_t** %8, align 8, !dbg !10804
  %21 = call %jl_value_t* @"julia_fact;23400"(%jl_value_t* inttoptr (i64 140544559367232 to %jl_value_t*), %jl_value_t** %8, i32 1), !dbg !10804
  store %jl_value_t* %21, %jl_value_t** %8, align 8, !dbg !10804
  %22 = call %jl_value_t* @"julia_*;23401"(%jl_value_t* inttoptr (i64 140544121124768 to %jl_value_t*), %jl_value_t** %7, i32 2), !dbg !10804
  %23 = load %jl_value_t** %6, align 8, !dbg !10804
  %24 = getelementptr inbounds %jl_value_t* %23, i64 0, i32 0, !dbg !10804
  store %jl_value_t** %24, %jl_value_t*** @jl_pgcstack, align 8, !dbg !10804
  ret %jl_value_t* %22, !dbg !10804
}
于 2014-08-11T19:54:53.857 回答