Julia 可以將數學運算符的參數提升為同一個類型,這些參數的類型曾經在整數和浮點數 ,數學運算和基本函數,類型,及方法中提到過。
在某種意義上,Julia 是“非自動類型提升”的:數學運算符只是有特殊語法的函數,函數的參數不會被自動轉換。但通過重載,仍能做到“自動”類型提升。
convert
函數用于將值轉換為各種類型。它有兩個參數:第一個是類型對象,第二個是要轉換的值;返回值是轉換為指定類型的值:
julia> x = 12
12
julia> typeof(x)
Int64
julia> convert(Uint8, x)
0x0c
julia> typeof(ans)
Uint8
julia> convert(FloatingPoint, x)
12.0
julia> typeof(ans)
Float64
遇到不能轉換時,convert
會引發(fā) “no method” 錯誤:
julia> convert(FloatingPoint, "foo")
ERROR: `convert` has no method matching convert(::Type{FloatingPoint}, ::ASCIIString)
in convert at base.jl:13
Julia 不做字符串和數字之間的類型轉換。
要定義新類型轉換,只需給 convert
提供新方法即可。下例將數值轉換為布爾值:
convert(::Type{Bool}, x::Number) = (x!=0)
此方法第一個參數的類型是單態(tài)類型, Bool
是 Type{Bool}
的唯一實例。此方法僅在第一個參數是 Bool
才調用。注意第一個參數使用的語法:參數的名稱在 ::
之前是省略的,只給出了參數的類型。這是 Julia 中對于一個函數參數,如果其類型是指定但該參數的值在函數體中從未使用過,那么語法會被使用,在這個例子中,因為參數是單態(tài)類型,就永遠不會有任何理由會在函數體中使用它的值。
轉換時檢查數值是否為 0 :
julia> convert(Bool, 1)
true
julia> convert(Bool, 0)
false
julia> convert(Bool, 1im)
ERROR: InexactError()
in convert at complex.jl:18
julia> convert(Bool, 0im)
false
實際使用的類型轉換都比較復雜,下例是 Julia 中的一個實現(xiàn):
convert{T<:Real}(::Type{T}, z::Complex) = (imag(z)==0 ? convert(T,real(z)) :
throw(InexactError()))
julia> convert(Bool, 1im)
InexactError()
in convert at complex.jl:40
繼續(xù) Julia 的 Rational
類型的案例研究, rational.jl 中類型轉換的聲明緊跟在類型聲明和構造函數之后:
convert{T<:Integer}(::Type{Rational{T}}, x::Rational) = Rational(convert(T,x.num),convert(T,x.den))
convert{T<:Integer}(::Type{Rational{T}}, x::Integer) = Rational(convert(T,x), convert(T,1))
function convert{T<:Integer}(::Type{Rational{T}}, x::FloatingPoint, tol::Real)
if isnan(x); return zero(T)//zero(T); end
if isinf(x); return sign(x)//zero(T); end
y = x
a = d = one(T)
b = c = zero(T)
while true
f = convert(T,round(y)); y -= f
a, b, c, d = f*a+c, f*b+d, a, b
if y == 0 || abs(a/b-x) <= tol
return a//b
end
y = 1/y
end
end
convert{T<:Integer}(rt::Type{Rational{T}}, x::FloatingPoint) = convert(rt,x,eps(x))
convert{T<:FloatingPoint}(::Type{T}, x::Rational) = convert(T,x.num)/convert(T,x.den)
convert{T<:Integer}(::Type{T}, x::Rational) = div(convert(T,x.num),convert(T,x.den))
前四個定義可確保 a//b == convert(Rational{Int64}, a/b)
。后兩個把分數轉換為浮點數和整數類型。
類型提升是指將各種類型的值轉換為同一類型。它與類型等級關系無關,例如,每個 Int32
值都可以被表示為 Float64
值,但 Int32
不是 Float64
的子類型。
Julia 使用 promote
函數來做類型提升,其參數個數可以是任意多,它返回同樣個數的同一類型的多元組;如果不能提升,則拋出異常。類型提升常用來將數值參數轉換為同一類型:
julia> promote(1, 2.5)
(1.0,2.5)
julia> promote(1, 2.5, 3)
(1.0,2.5,3.0)
julia> promote(2, 3//4)
(2//1,3//4)
julia> promote(1, 2.5, 3, 3//4)
(1.0,2.5,3.0,0.75)
julia> promote(1.5, im)
(1.5 + 0.0im,0.0 + 1.0im)
julia> promote(1 + 2im, 3//4)
(1//1 + 2//1*im,3//4 + 0//1*im)
浮點數值提升為最高的浮點數類型。整數值提升為本地機器的原生字長或最高的整數值類型。既有整數也有浮點數時,提升為可以包括所有值的浮點數類型。既有整數也有分數時,提升為分數。既有分數也有浮點數時,提升為浮點數。既有復數也有實數時,提升為適當的復數。
數值運算中,數學運算符 +
, -
, *
和 /
等方法定義,都“巧妙”的應用了類型提升。下例是 promotion.jl 中的一些定義:
+(x::Number, y::Number) = +(promote(x,y)...)
-(x::Number, y::Number) = -(promote(x,y)...)
*(x::Number, y::Number) = *(promote(x,y)...)
/(x::Number, y::Number) = /(promote(x,y)...)
promotion.jl 中還定義了其它算術和數學運算類型提升的方法,但 Julia 標準庫中幾乎沒有調用 promote
。 promote
一般用在外部構造方法中,便于使構造函數適應各種不同類型的參數。rational.jl 中提供了如下的外部構造方法:
Rational(n::Integer, d::Integer) = Rational(promote(n,d)...)
此方法的例子:
julia> Rational(int8(15),int32(-5))
-3//1
julia> typeof(ans)
Rational{Int64} (constructor with 1 method)
對自定義類型來說,最好由程序員給構造函數顯式提供所期待的類型。但處理數值問題時,做自動類型提升比較方便。
盡管可以直接給 promote
函數定義方法,但這太麻煩了。我們用輔助函數 promote_rule
來定義 promote
的行為。 promote_rule
函數接收類型對象對兒,返回另一個類型對象。此函數將參數中的類型的實例,提升為要返回的類型:
promote_rule(::Type{Float64}, ::Type{Float32} ) = Float64
提升后的類型不需要與函數的參數類型相同。下面是 Julia 標準庫中的例子:
promote_rule(::Type{Uint8}, ::Type{Int8}) = Int
promote_rule(::Type{Char}, ::Type{Uint8}) = Int32
不需要同時定義 promote_rule(::Type{A}, ::Type{B})
和 promote_rule(::Type{B}, ::Type{A})
—— promote_rule
函數在提升過程中隱含了對稱性。
promote_type
函數使用 promote_rule
函數來定義,它接收任意個數的類型對象,返回它們作為 promote
參數時,所應返回值的公共類型。因此可以使用 promote_type
來了解特定類型的組合會提升為哪種類型:
julia> promote_type(Int8, Uint16)
Int64
promote
使用 promote_type
來決定類型提升時要把參數值轉換為哪種類型。完整的類型提升機制可見 promotion.jl,一共有 35 行。
我們結束 Julia 分數類型的案例:
promote_rule{T<:Integer}(::Type{Rational{T}}, ::Type{T}) = Rational{T}
promote_rule{T<:Integer,S<:Integer}(::Type{Rational{T}}, ::Type{S}) = Rational{promote_type(T,S)}
promote_rule{T<:Integer,S<:Integer}(::Type{Rational{T}}, ::Type{Rational{S}}) = Rational{promote_type(T,S)}
promote_rule{T<:Integer,S<:FloatingPoint}(::Type{Rational{T}}, ::Type{S}) = promote_type(T,S)
更多建議: