顺便说一句,我认为在 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
}