因子函数在Python中起作用,为Julia返回0

时间:2014-01-15 01:56:15

标签: python julia

我在Python中定义了一个因子函数:

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

print(fact(100))

如下朱莉娅:

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?

6 个答案:

答案 0 :(得分:20)

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

100以来!需要大约526位,它显然会溢出Int64

您只需执行fact(BigInt(100))(假设您require)就可以解决此问题,当然您也可以在fact函数中进行转换。


曾经的Python曾经是相同的。它有单独的类型int,根据你的机器有16或32或64位,long是任意长度的。fact(100L)。如果您在Python 1.5上运行程序,它将像Julia一样包裹,或者引发异常。解决方案是致电long,或在fact函数内转换为int

但是,在2.x系列中的某些时候,Python将这两种类型绑定在一起,因此任何自动溢出的long都会成为long。然后,在3.0中,它完全合并了两种类型,因此不再有单独的int64


那么,为什么朱莉娅只是溢出而不是提出错误?

常见问题解答实际上解释了Why does Julia use native machine integer arithmetic。其中包括溢出时的环绕行为。


通过“原生机器算术”,人们通常意味着“C几乎在所有2s补充机器上做什么”。特别是在像Julia和Python这样的语言中,它们最初构建在C语言之上,并且非常接近金属。就朱莉娅而言,这不仅仅是一种“默认”,而是一种有意的选择。

在C中(至少和当时一样),实际上取决于实现如果溢出像OverflowError这样的有符号整数类型会发生什么......但是几乎任何本地使用2的补码算法的平台上都是如此(这几乎是你今天看到的任何平台),完全相同的事情发生:它只是截断前64位以上的所有内容,这意味着你从正面到负面回绕。实际上, unsigned 整数类型 required 在C中以这种方式工作。(同时,C也是这样工作的,因为这是大多数CPU的工作方式。)

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

在Python中,使每次乘法运算速度降低4倍(通常更少,但可能会更高)并不是什么大问题,因为Python花费更多时间获取字节码并取消整理对象而不是乘法。但朱莉娅意味着比这更快。

John Myles White在Computers are Machines中解释道:

  

在许多方面,朱莉娅通过尝试恢复从C语言转换为Python等语言时失去的一些力量,使自己与其他新语言脱颖而出。但转型带来了实质性的学习曲线。


但是还有另外一个原因:在许多情况下,溢出的带符号算法实际上很有用。不会像溢出无符号算术那样多(这就是为什么C在第一个ANSI规范之前定义了无符号算术的工作方式),但是有一些用例。

而且,即使你可能比想要翻转更频繁地想要类型转换,但是手动进行类型转换比翻转更容易 。如果您曾在Python中完成它,那么选择BigInt的操作数并获得正确的符号肯定容易出错;施放到str很难搞砸。


最后,在强类型语言中,如Python和Julia,类型稳定性很重要。 Python 3存在的原因之一是旧的unicode类型神奇地转换为int会导致问题。你的long类型奇怪地转换为int导致问题的情况要少得多,但它可能会发生(例如,当您从线路上获取值,或通过C API,并期望以相同的格式写出结果)。 Python的开发团队在进行long / {{1}}统一时引用了这一点,引用了“实用性超越纯度”和Zen的其他各种内容,并最终决定旧行为导致的问题多于新行为将。朱莉娅的设计做出了相反的决定。

答案 1 :(得分:16)

没人回答为什么朱莉娅的结果是0。

Julia不检查溢出的整数乘法,因此64位整数的乘法是执行mod 2 ^ 63。见this FAQ entry

当你写出阶乘的乘法时,你得到

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()函数。

答案 2 :(得分: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是一种带有OPTIONal Type Annotations和Inference的动态语言。

答案 3 :(得分:2)

Python自动使用可容纳任意大数的BigInt。在朱莉娅,你必须自己做。我认为它会像这样纠正

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

println(fact(BigInt(100))) 

答案 4 :(得分:0)

朱莉娅快速的原因之一是它们避免了会影响性能的功能。这是其中之一。在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) )

答案 5 :(得分: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的(LLVM)编译版本与BigInt事实()时,可以看到BigInt vs machine(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
}