數字

數字 在 Julia

7 個練習

關於 數字

Julia 是一種通用程式語言,可以用來處理大多數的程式設計工作。 不過實際上,它最主要的應用場景多半落在工程與科學領域。 快速、靈活且精密的數值運算,是它設計上的核心。

整數

整數是沒有小數點的「整」數。

在 Basics 概念裡,我們看到可以把整數值指定給變數,而不需要指定型別。

為了方便閱讀,可以用底線當作數字分隔符號。 編譯器會忽略它們。

julia> x = 3
3

julia> typeof(x)
Int64

julia> large_number = 1_234_567_890
1234567890

在內部,編譯器會選用對你的 CPU 最合適的有號整數型別。 在現代 PC 上通常會是 Int64,對大多數工作來說已經十分足夠。

型別會在之後的概念裡有更完整的討論。 現在,_數值_型別應該夠直覺,足以讓這份文件讀得通。

我們會在之後的概念看到,Julia 原生支援非常巨大的多維陣列。 這會同時考驗可用記憶體的容量,以及資料傳輸的頻寬。

為了讓程式設計師有更細緻的控制,指定想要的有號或無號整數型別不但可行,也相當常見。 許多先學過 Python 才來學 Julia 的人,可能會想把它拿來跟 NumPy 比較,而不是跟原生 Python 比。

julia> y = Int8(42)
42

julia> typeof(y)
Int8

julia> z = UInt32(1024)
0x00000400

注意 Julia 預設會以十六進位格式顯示無號整數,因為無號型別常常用於低階的位元運算。

整數也可以用二進位、八進位或十六進位輸入,前綴分別是 0b、0o 和 0x。

julia> a = 0x10
0x10

julia> Int(a)
16

julia> b = 0b1010 # displays as hexadecimal
0x0a

整數溢位與BigInt

每種整數型別都有它能儲存的最大值與最小值:

julia> typemax(Int8)
127

julia> typemin(Int8)
-128

超出這個有效範圍就會造成整數溢位,結果看起來會非常奇怪。

julia> c = Int8(126)
126

julia> c * c
4

我們大概都會同意,126 * 126 應該_不_等於 4!

要詳細解釋,需要了解 CPU 暫存器如何儲存整數,這超出了本概念的範疇。

使用「更寬」的型別可以把問題降到最低,最大可以到 Int128 或 UInt128(「更寬」的意思會在下面討論)。 不過,它們在遇到溢位問題之前,都各有硬性上限。

其中一個解法是使用BigInt型別,它只受限於你電腦的記憶體。 這是 Julia 對任意精度算術的實作。

julia> 2 ^ 70
0

julia> big = BigInt(2) ^ 70
1180591620717411303424

julia> UInt128(big)
0x00000000000000400000000000000000

因為 2 的 70 次方放不進 Int64,上面例子中的第一個計算會失敗:不過要注意,並不會出現任何錯誤訊息。 寫程式的人請小心!

把 2 轉型成BigInt就會得到正確答案,用十六進位顯示也更能看出這是一個 2 的次方。

浮點數

不出意料,浮點數可以有小數點,以及小數點後的小數部分。

julia> f = 3.45
3.45

julia> typeof(f)
Float64

當然,也支援科學記號。

julia> avogadro = 6.02e23
6.02e23

和整數一樣,預設型別對大多數用途都沒問題,但還有其他有號型別可用。 並沒有_無號_浮點型別。

有個捷徑:加上 f0 後綴就能建立 Float32 值。

julia> f32 = 4.56f0
4.56f0

julia> typeof(f32)
Float32

其他大小的浮點數則需要明確轉型,和整數一樣。

最大值和最小值可能會讓你嚇一跳:

julia> typemax(Float64)
Inf

julia> typemin(Float64)
-Inf

無限大是一個合法的值!

不過,浮點數的可用範圍有限,數值太大就直接指定為 Inf,太小則捨入成 0.0。

我們可以用另一組函式來看看這些極限:大約是 ± 10 ^ 308。

julia> floatmax(Float64)
1.7976931348623157e308

julia> floatmin(Float64)
2.2250738585072014e-308

此外,精度也有限,能表示的有效位數雖然多但仍是有限個(Float64 大約是 15 位)。

算術運算子

如同 Basics 概念中所討論的,算術運算子大多和我們小時候學的標準算術一樣。 注意,指數運算用的是^,不是**(兩者在其他語言裡都很常見)。

2 + 3  # 5 (addition)
2 - 3  # -1 (subtraction)
2 * 3  # 6 (multiplication)
8 / 2  # 4.0 (division)
8 % 3  # 2 (remainder)
2 ^ 3  # 8 (exponentiation)

不過,有幾個 Julia 特有的細節值得討論。

乘法

julia> x = 4.2
4.2

julia> 2 * x
8.4

julia> 2x
8.4

julia> 2.4x
10.08

這可能有點出乎意料。

和大多數其他程式語言一樣,你隨時可以用*作為中綴運算子。

不過,Julia 的設計者相信,程式碼應該盡可能看起來像數學算式。

因為變數名稱必須以字母開頭,所以在名稱前面加上數字(整數或浮點數)會被視為隱含的乘法。

舉例來說,如果我們想要球體的表面積,除了 4 * pi * r * r,也可以這樣寫:

julia> surface(r) = 4π * r^2
surface (generic function with 1 method)

julia> surface(3)
113.09733552923255

雖然 π 是內建的常數,它同時也是一個(希臘)字母。 所以剖析器仍然需要一個明確的*,把π和r分開。

除法

用/作為中綴運算子,永遠會得到浮點數結果,即使輸入是整數也一樣。

整數除法則有更多選擇:

julia> 10 / 3
3.3333333333333335

julia> div(10, 3)
3

julia> 10 ÷ 3
3

julia> 10 // 3
10//3

div()函式用於整數除法,結果會朝零截斷:正數向下、負數向上。

作為同義寫法,我們可以用中綴運算子÷,同樣是為了讓它看起來更像數學。 如果你用的編輯器看得懂 Julia,輸入 \div 然後按 <Tab> 鍵即可。

//運算子之後在課程大綱裡會有專門的一個概念。

現在我們只要說,//的結果是「有理數」,這是大多數人稱為分數的東西的正式名稱。

分子和分母的公因數會被約掉,得出兩個整數的比值,也就是所謂的「最簡分數」。

julia> rationalnum = 22 // 6
11//3

julia> typeof(rationalnum)
Rational{Int64}

我們有了有理數。 那「無理」數呢?

julia> π
π = 3.1415926535897...

julia> typeof(π)
Irrational{:π}

無理數是無法化為整數比值的數。 常見的例子包括π、e(歐拉數),以及許多根號,例如√2(2 的平方根)。

Julia 試著_好好地_做數學。

數值型別的轉換

這常常會自動發生:

julia> x = 2 + 3.5
5.5

julia> typeof(x)
Float64

我們把 Int64 加上 Float64,得到 Float64 的結果。

事實上,整數在做加法之前,就已經悄悄被轉成 Float64 了。

Julia 有個數值型別「寬度」的概念。

  • 在整數之間、以及在浮點數之間,這就只是儲存所需的位元數。 因此 Int64 比 Int16 更寬。
  • 浮點數被視為比整數更寬,因為浮點數能儲存小數部分。

如果一個運算式裡混用了多種型別,每個值都會視需要被「提升」到所用到的最寬型別。

要強制轉換,我們可以把整數轉型成特定型別,例如Float64(5)。

或者直接用float(5),讓編譯器挑一個合適的型別。

浮點數轉整數的轉換無可避免地比較複雜。 小數點後面的部分你想怎麼處理?

  • round()函式會轉成最接近的整數,遇到像 4.5 這種剛好在中間的情況,會捨入到最接近的_偶數_。
  • floor()向下捨入,ceil()向上捨入,trunc()朝零捨入。
  • 直接轉型,例如用Int32(),會失敗並丟出 InexactError。

不過,這些函式預設不會回傳你可能想要的整數型別。 你可以指定想要的輸出型別。

julia> round(4.5)
4.0

julia> round(Int64, 4.5)
4

julia> round(Int, 4.5)  # => default integer type
4

julia> ceil(Int16, 4.3)
5

也可以用digits關鍵字,指定要捨入到小數點後幾位。

julia> round(π, digits=10)
3.1415926536

詳情請參閱手冊。

除以零

這想必只會丟出錯誤吧? 事實上,情況沒那麼單純。

用÷或//做整數除法,會如你所料產生錯誤。

用/做浮點除法,走的則是比較像工程的做法,而不是標準的資訊科學做法:

julia> 2 / 0
Inf

julia> 0 / 0
NaN

如前一節所討論的,無限大在 Julia 裡是合法的浮點數,以Inf表示。

當分子也是零時,結果在數學上是未定義的。 Julia 會把它當成「非數值」,以NaN表示。

如果這看起來很奇怪,想想你是在處理大量真實世界(因此常常相當雜亂)的資料陣列。 為了繼續推進,最好的做法就是把有問題的值標記起來,然後繼續往下走。

無止盡地手動檢查每個值,寫起來很煩人,也肯定會拖累執行時的效能。

只要有一點小狀況就停下來丟錯誤訊息,會讓你的程式_非常_不受使用者歡迎!

比較浮點數值

如 Conditionals概念所述,相等性測試通常用==運算子來做。

這對整數、字元、字串等都運作得很好。 不過,浮點數值的精度有限,用不同方式算出同一個結果,也可能得到略有差異的值。 對 Float64 來說,差異通常會落在第 15 位有效數字附近:差距很小,但並不「相等」。

傳統上給程式設計師的建議是:_千萬不要_對浮點數值使用==,因為結果無法預測。

一個廣為使用的替代做法,是拿兩者差值的絕對值去和某個容許誤差(通常叫 epsilon 或 ϵ)比較。 所以不要用 a == b,改用 abs(a - b) < epsilon。

Julia 用 isapprox()函式提供了更乾淨的替代方案。

要用絕對容差,語法是isapprox(a, b, atol=epsilon)。 這種情況下一定要用atol關鍵字。

相對容差通常更有用。 這是預設值,所以isapprox(a, b)會試著為rtol挑一個合理的值(規則相當複雜)。

相對容差也可以指定,用被比較數值的比例來表示。 所以isapprox(a, b, rtol=0.01會測試兩個值是否在彼此的 1% 以內。

依照 Julia 慣例,有個數學運算子等同於預設情況:a ≈ b(這個運算子輸入方式是 \approx 再按 <tab>)。

之後相關的概念

除了有理數之外,課程大綱後面的部分還會討論:

透過 GitHub 編輯 連結會在新視窗或分頁中開啟

學習 數字

練習已鎖定

再解鎖 2 個練習,就能練習 數字