整數和浮點數
整數和浮點數值是算術和運算的基本構建塊。此類數值的內建表示稱為數值基本型,而整數和浮點數值在程式碼中作為立即數值的表示則稱為數值文字。例如,1
是整數文字,而 1.0
是浮點文字;它們作為物件的二進位記憶體內部表示是數值基本型。
Julia 提供廣泛的原始數值類型,並定義了完整的算術和位元運算子,以及標準數學函數。這些函數直接對應到現代電腦原生支援的數值類型和運算,因此讓 Julia 能夠充分利用計算資源。此外,Julia 提供軟體支援 任意精度算術,可以處理無法在原生硬體表示中有效表示的數值運算,但代價是效能相對較慢。
以下是 Julia 的原始數值類型
- 整數類型
類型 | 有號? | 位元數 | 最小值 | 最大值 |
---|---|---|---|---|
Int8 | ✓ | 8 | -2^7 | 2^7 - 1 |
UInt8 | 8 | 0 | 2^8 - 1 | |
Int16 | ✓ | 16 | -2^15 | 2^15 - 1 |
UInt16 | 16 | 0 | 2^16 - 1 | |
Int32 | ✓ | 32 | -2^31 | 2^31 - 1 |
UInt32 | 32 | 0 | 2^32 - 1 | |
Int64 | ✓ | 64 | -2^63 | 2^63 - 1 |
UInt64 | 64 | 0 | 2^64 - 1 | |
Int128 | ✓ | 128 | -2^127 | 2^127 - 1 |
UInt128 | 128 | 0 | 2^128 - 1 | |
Bool | N/A | 8 | false (0) | true (1) |
- 浮點類型
類型 | 精度 | 位元數 |
---|---|---|
Float16 | half | 16 |
Float32 | single | 32 |
Float64 | double | 64 |
此外,複數和有理數 的完整支援建立在這些原始數值類型之上。由於具備彈性且使用者可延伸的 類型提升系統,所有數值類型都可以自然互動,而無需明確轉型。
整數
文字整數以標準方式表示
julia> 1
1
julia> 1234
1234
整數文字的預設類型取決於目標系統是 32 位元架構還是 64 位元架構
# 32-bit system:
julia> typeof(1)
Int32
# 64-bit system:
julia> typeof(1)
Int64
Julia 內部變數 Sys.WORD_SIZE
指示目標系統是 32 位元還是 64 位元
# 32-bit system:
julia> Sys.WORD_SIZE
32
# 64-bit system:
julia> Sys.WORD_SIZE
64
Julia 也定義了類型 Int
和 UInt
,它們分別是系統有號和無號原生整數類型的別名
# 32-bit system:
julia> Int
Int32
julia> UInt
UInt32
# 64-bit system:
julia> Int
Int64
julia> UInt
UInt64
無法僅使用 32 位元表示但可以用 64 位元表示的較大整數文字,總是會建立 64 位元整數,而不論系統類型為何
# 32-bit or 64-bit system:
julia> typeof(3000000000)
Int64
無號整數使用 0x
前置詞和十六進位(16 進位制)數字 0-9a-f
(大寫數字 A-F
也可用於輸入)進行輸入和輸出。無號值的長度由所使用的十六進位數字數量決定
julia> x = 0x1
0x01
julia> typeof(x)
UInt8
julia> x = 0x123
0x0123
julia> typeof(x)
UInt16
julia> x = 0x1234567
0x01234567
julia> typeof(x)
UInt32
julia> x = 0x123456789abcdef
0x0123456789abcdef
julia> typeof(x)
UInt64
julia> x = 0x11112222333344445555666677778888
0x11112222333344445555666677778888
julia> typeof(x)
UInt128
此行為基於一個觀察,當有人使用未簽名的十六進制字面值表示整數值時,通常會使用它們來表示固定的數字位元組序列,而非僅僅是一個整數值。
二進制和八進制字面值也受支援
julia> x = 0b10
0x02
julia> typeof(x)
UInt8
julia> x = 0o010
0x08
julia> typeof(x)
UInt8
julia> x = 0x00000000000000001111222233334444
0x00000000000000001111222233334444
julia> typeof(x)
UInt128
與十六進制字面值一樣,二進制和八進制字面值會產生未簽名的整數型別。二進制資料項的大小為所需的最小大小,如果字面值的開頭數字不是 0
。在開頭為零的情況下,大小由字面值的所需最小大小決定,其長度相同但開頭數字為 1
。這表示
0x1
和0x12
是UInt8
字面值,0x123
和0x1234
是UInt16
字面值,0x12345
和0x12345678
是UInt32
字面值,0x123456789
和0x1234567890adcdef
是UInt64
字面值,依此類推。
即使有開頭的零數字不會對值造成影響,它們仍會計算在決定字面值的儲存大小中。因此 0x01
是 UInt8
,而 0x0001
是 UInt16
。
這允許使用者控制大小。
對編碼成過大而無法表示為 UInt128
值的整數的未簽名字面值 (從 0x
開始) 將會建構 BigInt
值。這不是未簽名型別,但它是唯一內建的型別,夠大到足以表示如此大的整數值。
二進制、八進制和十六進制字面值可以由緊接在未簽名字面值之前的 -
符號簽署。它們會產生與未簽名字面值相同大小的未簽名整數,並具有值的二補數
julia> -0x2
0xfe
julia> -0x0002
0xfffe
整數等原始數字類型的最小和最大可表示值由 typemin
和 typemax
函數給出
julia> (typemin(Int32), typemax(Int32))
(-2147483648, 2147483647)
julia> for T in [Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128]
println("$(lpad(T,7)): [$(typemin(T)),$(typemax(T))]")
end
Int8: [-128,127]
Int16: [-32768,32767]
Int32: [-2147483648,2147483647]
Int64: [-9223372036854775808,9223372036854775807]
Int128: [-170141183460469231731687303715884105728,170141183460469231731687303715884105727]
UInt8: [0,255]
UInt16: [0,65535]
UInt32: [0,4294967295]
UInt64: [0,18446744073709551615]
UInt128: [0,340282366920938463463374607431768211455]
typemin
和 typemax
傳回的值永遠是給定參數類型。(以上表達式使用了一些尚未介紹的功能,包括 for 迴圈、字串 和 內插,但對於具備一些程式設計經驗的使用者而言,應足夠容易理解。)
溢位行為
在 Julia 中,超過給定類型的最大可表示值會導致換行行為
julia> x = typemax(Int64)
9223372036854775807
julia> x + 1
-9223372036854775808
julia> x + 1 == typemin(Int64)
true
因此,Julia 整數的算術實際上是一種 模算術 形式。這反映了在現代電腦上實作的整數底層算術的特性。在可能發生溢位的應用程式中,必須明確檢查溢位產生的換行;否則,建議改用 任意精度算術 中的 BigInt
類型。
以下是溢位行為和如何潛在解決它的範例
julia> 10^19
-8446744073709551616
julia> big(10)^19
10000000000000000000
除法錯誤
整數除法(div
函數)有兩個例外情況:除以零,以及將最低負數 (typemin
) 除以 -1。這兩個情況都會擲出 DivideError
。當第二個參數為零時,餘數和模數函數(rem
和 mod
)會擲出 DivideError
。
浮點數
文字浮點數以標準格式表示,必要時使用E 記法
julia> 1.0
1.0
julia> 1.
1.0
julia> 0.5
0.5
julia> .5
0.5
julia> -1.23
-1.23
julia> 1e10
1.0e10
julia> 2.5e-4
0.00025
上述結果皆為Float64
值。文字Float32
值可透過在 e
的位置寫入 f
來輸入
julia> x = 0.5f0
0.5f0
julia> typeof(x)
Float32
julia> 2.5f-4
0.00025f0
值可輕鬆轉換為Float32
julia> x = Float32(-1.5)
-1.5f0
julia> typeof(x)
Float32
十六進位浮點數文字也為有效值,但僅限於Float64
值,且 p
必須出現在基底 2 指數之前
julia> 0x1p0
1.0
julia> 0x1.8p3
12.0
julia> x = 0x.4p-1
0.125
julia> typeof(x)
Float64
半精度浮點數也受支援 (Float16
),但它們在軟體中實作,並使用Float32
進行計算。
julia> sizeof(Float16(4.))
2
julia> 2*Float16(4.)
Float16(8.0)
底線 _
可用作數字分隔符號
julia> 10_000, 0.000_000_005, 0xdead_beef, 0b1011_0010
(10000, 5.0e-9, 0xdeadbeef, 0xb2)
浮點數零
浮點數有兩個零,正零和負零。它們彼此相等,但有不同的二進制表示法,可使用bitstring
函數查看
julia> 0.0 == -0.0
true
julia> bitstring(0.0)
"0000000000000000000000000000000000000000000000000000000000000000"
julia> bitstring(-0.0)
"1000000000000000000000000000000000000000000000000000000000000000"
特殊浮點數值
有三個指定的標準浮點數值不對應到實數線上的任何一點
Float16 | Float32 | Float64 | 名稱 | 說明 |
---|---|---|---|---|
Inf16 | Inf32 | Inf | 正無窮大 | 大於所有有限浮點數值的值 |
-Inf16 | -Inf32 | -Inf | 負無窮大 | 小於所有有限浮點數值的一個值 |
NaN16 | NaN32 | NaN | 非數字 | 不== 任何浮點數值(包括它自己)的一個值 |
如需進一步討論這些非有限浮點數值如何相對於彼此和其他浮點數值排序,請參閱數值比較。根據IEEE 754 標準,這些浮點數值是特定算術運算的結果
julia> 1/Inf
0.0
julia> 1/0
Inf
julia> -5/0
-Inf
julia> 0.000001/0
Inf
julia> 0/0
NaN
julia> 500 + Inf
Inf
julia> 500 - Inf
-Inf
julia> Inf + Inf
Inf
julia> Inf - Inf
NaN
julia> Inf * Inf
Inf
julia> Inf / Inf
NaN
julia> 0 * Inf
NaN
julia> NaN == NaN
false
julia> NaN != NaN
true
julia> NaN < NaN
false
julia> NaN > NaN
false
julia> (typemin(Float16),typemax(Float16))
(-Inf16, Inf16)
julia> (typemin(Float32),typemax(Float32))
(-Inf32, Inf32)
julia> (typemin(Float64),typemax(Float64))
(-Inf, Inf)
機器 epsilon
大多數實數無法以浮點數精確表示,因此對於許多目的而言,了解兩個相鄰可表示浮點數之間的距離非常重要,這通常稱為機器 epsilon。
Julia 提供 eps
,它提供 1.0
和下一個較大的可表示浮點數值之間的距離
julia> eps(Float32)
1.1920929f-7
julia> eps(Float64)
2.220446049250313e-16
julia> eps() # same as eps(Float64)
2.220446049250313e-16
這些值分別為 2.0^-23
和 2.0^-52
,分別為 Float32
和 Float64
值。 eps
函數也可以將浮點數值作為參數,並提供該值與下一個可表示浮點數值之間的絕對差。也就是說,eps(x)
會產生與 x
相同類型的值,使得 x + eps(x)
是大於 x
的下一個可表示浮點數值
julia> eps(1.0)
2.220446049250313e-16
julia> eps(1000.)
1.1368683772161603e-13
julia> eps(1e-27)
1.793662034335766e-43
julia> eps(0.0)
5.0e-324
兩個相鄰可表示浮點數之間的距離並非恆定,而是對較小的值較小,對較大的值較大。換句話說,可表示浮點數在接近零的實數線上最密集,隨著遠離零的距離增加而呈指數級稀疏。根據定義,eps(1.0)
與 eps(Float64)
相同,因為 1.0
是 64 位元浮點數值。
Julia 也提供 nextfloat
和 prevfloat
函數,分別傳回比引數大或小的下一個可表示浮點數
julia> x = 1.25f0
1.25f0
julia> nextfloat(x)
1.2500001f0
julia> prevfloat(x)
1.2499999f0
julia> bitstring(prevfloat(x))
"00111111100111111111111111111111"
julia> bitstring(x)
"00111111101000000000000000000000"
julia> bitstring(nextfloat(x))
"00111111101000000000000000000001"
此範例強調了一般原則,即相鄰的可表示浮點數也具有相鄰的二進位整數表示法。
捨入模式
如果數字沒有精確的浮點數表示法,則必須將其捨入為適當的可表示值。但是,根據 IEEE 754 標準 中提供的捨入模式,可以視需要變更執行此捨入的方式。
使用的預設模式始終為 RoundNearest
,它會捨入至最接近的可表示值,而捨入至最接近且最低有效位元為偶數的值。
背景與參考資料
浮點數運算包含許多細微差別,對於不熟悉低階實作細節的使用者而言,可能會感到驚訝。但是,大多數科學運算書籍以及下列參考資料都詳細說明了這些細微差別
- 浮點數運算的權威指南為 IEEE 754-2008 標準;然而,無法免費於線上取得。
- 對於浮點數表示法的簡潔但清晰的說明,請參閱 John D. Cook 的 文章,以及他對此表示法與實數理想化抽象在行為上差異所產生的一些問題的 介紹。
- 另外推薦 Bruce Dawson 的 一系列浮點數部落格文章。
- 對於浮點數和在使用浮點數運算時所遭遇的數值精確度問題的深入探討,請參閱 David Goldberg 的論文 每個電腦科學家都應該知道的浮點數運算。
- 對於浮點數的歷史、基本原理和問題的更廣泛文件,以及對數值運算中許多其他主題的討論,請參閱 William Kahan 的 文集,他通常被稱為「浮點數之父」。特別感興趣的可能是 與浮點數老人的訪談。
任意精度運算
為了允許使用任意精度的整數和浮點數進行運算,Julia 封裝了 GNU 多重精度運算函式庫 (GMP) 和 GNU MPFR 函式庫。Julia 中提供了 BigInt
和 BigFloat
類型,分別用於任意精度的整數和浮點數。
建構函式用於從原始數值類型建立這些類型,而 字串文字 @big_str
或 parse
可用於從 AbstractString
建立這些類型。當 BigInt
太大而無法使用其他內建整數類型時,也可以將其輸入為整數文字。請注意,由於 Base
中沒有無符號任意精度整數類型(在大部分情況下 BigInt
就已足夠),因此可以使用十六進位、八進位和二進位文字(除了十進位文字)。
建立後,它們會參與所有其他數值類型的算術運算,這要歸功於 Julia 的 類型提升和轉換機制
julia> BigInt(typemax(Int64)) + 1
9223372036854775808
julia> big"123456789012345678901234567890" + 1
123456789012345678901234567891
julia> parse(BigInt, "123456789012345678901234567890") + 1
123456789012345678901234567891
julia> string(big"2"^200, base=16)
"100000000000000000000000000000000000000000000000000"
julia> 0x100000000000000000000000000000000-1 == typemax(UInt128)
true
julia> 0x000000000000000000000000000000000
0
julia> typeof(ans)
BigInt
julia> big"1.23456789012345678901"
1.234567890123456789010000000000000000000000000000000000000000000000000000000004
julia> parse(BigFloat, "1.23456789012345678901")
1.234567890123456789010000000000000000000000000000000000000000000000000000000004
julia> BigFloat(2.0^66) / 3
2.459565876494606882133333333333333333333333333333333333333333333333333333333344e+19
julia> factorial(BigInt(40))
815915283247897734345611269596115894272000000000
但是,上述原始類型和 BigInt
/BigFloat
之間的類型提升並非自動進行,必須明確陳述。
julia> x = typemin(Int64)
-9223372036854775808
julia> x = x - 1
9223372036854775807
julia> typeof(x)
Int64
julia> y = BigInt(typemin(Int64))
-9223372036854775808
julia> y = y - 1
-9223372036854775809
julia> typeof(y)
BigInt
可以透過呼叫 setprecision
和 setrounding
來變更 BigFloat
運算的預設精度(有效數字的位元數)和捨入模式,而後續的所有計算都會考量這些變更。或者,也可以只在特定程式碼區塊執行時變更精度或捨入,方法是使用具有 do
區塊的相同函式
julia> setrounding(BigFloat, RoundUp) do
BigFloat(1) + parse(BigFloat, "0.1")
end
1.100000000000000000000000000000000000000000000000000000000000000000000000000003
julia> setrounding(BigFloat, RoundDown) do
BigFloat(1) + parse(BigFloat, "0.1")
end
1.099999999999999999999999999999999999999999999999999999999999999999999999999986
julia> setprecision(40) do
BigFloat(1) + parse(BigFloat, "0.1")
end
1.1000000000004
數值文字係數
為了讓常見的數值公式和表達式更清楚,Julia 允許變數緊接在數值文字之前,表示乘法。這讓多項式表達式的撰寫更簡潔
julia> x = 3
3
julia> 2x^2 - 3x + 1
10
julia> 1.5x^2 - .5x + 1
13.0
這也讓指數函式的撰寫更優雅
julia> 2^2x
64
數字文字係數的優先順序略低於單元運算子,例如取負。因此,-2x
會剖析為 (-2) * x
,而 √2x
會剖析為 (√2) * x
。不過,數字文字係數與冪次運算結合時,剖析方式類似於單元運算子。例如,2^3x
會剖析為 2^(3x)
,而 2x^3
會剖析為 2*(x^3)
。
數字文字也可以作為括號表達式的係數
julia> 2(x-1)^2 - 3(x-1) + 1
3
用於隱式乘法的數字文字係數的優先順序高於其他二元運算子,例如乘法 (*
)、除法 (/
、\
和 //
)。這表示,例如,1 / 2im
等於 -0.5im
,而 6 // 2(2 + 1)
等於 1 // 1
。
此外,括號表達式可以用作變數的係數,表示將表達式乘以變數
julia> (x-1)x
6
不過,兩個括號表達式並置,或在括號表達式前放置變數,都不能用來表示乘法
julia> (x-1)(x+1)
ERROR: MethodError: objects of type Int64 are not callable
julia> x(x+1)
ERROR: MethodError: objects of type Int64 are not callable
兩個表達式都會被解釋為函數應用:任何非數字文字的表達式,在緊接在括號之後時,會被解釋為對括號中值的函數應用(請參閱 函數 以進一步了解函數)。因此,在這些情況下,由於左邊的值不是函數,因此會發生錯誤。
上述語法增強功能大幅減少在撰寫常見數學公式時產生的視覺雜訊。請注意,數字文字係數與它所乘的識別碼或括號表達式之間不能有空白。
語法衝突
並置的字面係數語法可能會與某些數字字面語法產生衝突:十六進位、八進位和二進位整數字面,以及浮點字面中的工程表示法。以下是語法衝突發生的幾個情況
- 十六進位整數字面表達式
0xff
可以解釋為數字字面0
乘以變數xff
。八進位和二進位字面(例如0o777
或0b01001010
)也會產生類似的歧義。 - 浮點字面表達式
1e10
可以解釋為數字字面1
乘以變數e10
,對應的E
形式也類似。 - 32 位浮點字面表達式
1.5f22
可以解釋為數字字面1.5
乘以變數f22
。
在所有情況下,歧義都傾向於解釋為數字字面
- 以
0x
/0o
/0b
開頭的表達式始終是十六進位/八進位/二進位字面。 - 以數字字面後接
e
或E
開頭的表達式始終是浮點字面。 - 以數字字面後接
f
開頭的表達式始終是 32 位浮點字面。
與基於歷史原因在數字字面中等於 e
的 E
不同,F
只是一個字母,在數字字面中不會像 f
那樣運作。因此,以數字字面後接 F
開頭的表達式會被解釋為數字字面乘以一個變數,這表示例如 1.5F22
等於 1.5 * F22
。
字面零和一
Julia 提供函數,會傳回對應於指定類型或給定變數類型的字面 0 和 1。
函數 | 說明 |
---|---|
zero(x) | 型別 x 的字面零或變數 x 的型別 |
one(x) | 型別 x 或變數 x 的型別的字面一 |
這些函式在 數字比較 中很有用,可避免不必要的 型別轉換 所造成的負擔。
範例
julia> zero(Float32)
0.0f0
julia> zero(1.0)
0.0
julia> one(Int32)
1
julia> one(BigFloat)
1.0