編碼 · 程式語言
Fortran 入門教學
簡答
Fortran 是為數值與科學運算而打造的編譯式、靜態型別語言。現代 Fortran(Fortran 90 及之後的標準)具備自由格式原始碼、模組、動態陣列與強大的整個陣列運算,編譯器會把它轉成非常快的機器碼。安裝免費的 gfortran 編譯器,把程式碼存成 .f90 檔案,再用 gfortran hello.f90 -o hello 建置。
Fortran 是什麼、誰在使用
Fortran(「formula translation」,公式翻譯)於 1950 年代由 John Backus 領導的 IBM 團隊開發,是至今仍在積極使用的最古老的程式語言之一。它由 ISO 標準化;主要版本包括 Fortran 90、95、2003、2008、2018 與 2023。舊式的固定格式風格(大寫的 FORTRAN 77,程式碼從第 7 欄開始)仍然可以編譯,但新的程式碼使用本頁所示的自由格式語法。
Fortran 用在需要大量數值運算的場合:天氣與氣候模型、計算流體力學、物理與化學模擬,以及許多在超級電腦上執行的程式。BLAS 與 LAPACK 線性代數函式庫的參考版本是用 Fortran 寫成的,NumPy 與 MATLAB 等工具的底層就用到它們。
安裝編譯器並執行 Hello, world
gfortran 屬於 GCC,是免費的,可在 Linux(套件 gfortran)、macOS(Homebrew gcc)與 Windows(MSYS2 或 WSL)上取得。Intel 的 ifx 與 LLVM Flang 則是其他編譯器。Fortran Package Manager(fpm)可以建立並建置專案。
program hello
implicit none
print *, "Hello, world!"
end program hellogfortran hello.f90 -o hello
./hello
# Hello, world!print * 使用 list-directed 輸出,會替你決定間距(注意開頭的空格)。Fortran 不分大小寫:PRINT、Print 與 print 是相同的。
變數與基本型別
所有宣告都要放在程式單元的最上方,在可執行陳述式之前。請務必寫 implicit none:沒有它的話,未宣告且以 I 到 N 開頭的名稱會默默地變成整數,其他則變成實數。
| 型別 | 宣告範例 | 說明 |
|---|---|---|
| integer | integer :: count = 3 | 通常是 32 位元;64 位元請用 integer(int64) |
| real | real :: x | 通常是單精度 |
| real(real64) | real(real64) :: y | 雙精度,科學運算的常用選擇 |
| complex | complex :: z = (1.0, 2.0) | 內建複數 |
| logical | logical :: done = .false. | .true. / .false.,運算子為 .and.、.or.、.not. |
| character | character(len=20) :: name | 固定長度字串,以空格補齊 |
program types
use, intrinsic :: iso_fortran_env, only: int64, real64
implicit none
integer :: count = 3
integer(int64) :: big = 9000000000_int64
real(real64) :: third
logical :: done = .false.
character(len=:), allocatable :: name
third = 1.0_real64 / 3.0_real64
name = "Ana"
print *, count, big, third, done, name
end program types後綴 _real64 用來設定字面值的 kind(精度)。沒有它的話,1.0 是單精度常數,指派給雙精度變數時會損失位數。
流程控制
program control
implicit none
integer :: i, total, score
total = 0
do i = 1, 10
if (mod(i, 2) == 0) cycle ! skip even numbers
total = total + i
end do
print *, "sum of odd numbers:", total ! 25
score = 85
if (score >= 90) then
print *, "A"
else if (score >= 80) then
print *, "B"
else
print *, "C"
end if
select case (score)
case (90:)
print *, "excellent"
case (80:89)
print *, "good"
case default
print *, "keep going"
end select
end program controldo i = 1, 10 包含兩端,選用的第三個值可設定步進(do i = 10, 1, -1)。cycle 跳到下一次迭代,exit 離開迴圈。也可以使用 do while (condition),以及搭配 exit 的單純 do。註解以 ! 開頭。
函式、副程式與模組
Fortran 有兩種程序:函式會回傳值,可用在運算式中;副程式(subroutine)以 call 呼叫,並透過引數回傳結果。請把程序放在模組中,讓編譯器能檢查每一次呼叫。
module geometry
implicit none
contains
function circle_area(r) result(a)
real, intent(in) :: r
real :: a
a = 3.14159 * r**2
end function circle_area
subroutine swap(a, b)
integer, intent(inout) :: a, b
integer :: tmp
tmp = a
a = b
b = tmp
end subroutine swap
end module geometry
program demo
use geometry
implicit none
integer :: x, y
x = 1
y = 2
print *, circle_area(2.0) ! about 12.566
call swap(x, y)
print *, x, y ! 2 1
end program demo引數以傳參考的方式傳遞,所以程序可以更改呼叫者的變數。intent(in)、intent(out) 與 intent(inout) 用來說明並強制每個引數可以做什麼。** 是次方運算子。
陣列:Fortran 的招牌特色
陣列是 Fortran 的核心。索引預設從 1 開始(允許任何下界),算術運算可以對整個陣列進行而不需迴圈,切片使用 a(start:end),許多內建函式也可直接對陣列運算。
program arrays
implicit none
integer :: i
real :: a(5), b(5)
real, allocatable :: c(:)
integer :: m(2, 3)
a = [(real(i), i = 1, 5)] ! 1.0 2.0 3.0 4.0 5.0
b = 2.0 * a + 1.0 ! whole-array arithmetic
print *, b(2:4) ! 5.0 7.0 9.0
print *, sum(a), maxval(b) ! 15.0 11.0
allocate(c(10))
c = 0.0
print *, size(c) ! 10
deallocate(c)
m = reshape([1, 2, 3, 4, 5, 6], [2, 3])
print *, m(2, 1) ! 2 (column-major order)
end program arrays多維陣列以欄優先(column-major)順序儲存:第一個索引在記憶體中變動最快,與 C 相反。為了速度,請讓最內層迴圈遍歷第一個索引。其他實用的內建函式還有 matmul、dot_product、transpose、minval 與 count。現代 Fortran 也提供 do concurrent 迴圈與 coarray,用於平行程式。
錯誤處理
Fortran 沒有例外。可能失敗的陳述式會接受狀態引數:輸入輸出用 iostat,allocate 用 stat。請檢查其值並做出反應;如果省略,發生錯誤時程式會停止。error stop 會以錯誤結束程式。
program parse
implicit none
character(len=20) :: text
integer :: n, ios
text = "abc"
read(text, *, iostat=ios) n
if (ios /= 0) then
print *, "not a number: ", trim(text)
else
print *, "n =", n
end if
end program parse如上所示,從字元變數讀取稱為內部讀取(internal read),是把字串轉成數字的慣用方式。
完整的小程式:平均值與標準差
module stats
implicit none
integer, parameter :: dp = kind(1.0d0)
contains
pure function mean(x) result(m)
real(dp), intent(in) :: x(:)
real(dp) :: m
m = sum(x) / size(x)
end function mean
pure function std_dev(x) result(s)
real(dp), intent(in) :: x(:)
real(dp) :: s
s = sqrt(sum((x - mean(x))**2) / (size(x) - 1))
end function std_dev
end module stats
program main
use stats
implicit none
real(dp) :: values(5)
values = [2.0_dp, 4.0_dp, 4.0_dp, 5.0_dp, 7.0_dp]
print '(a, f6.3)', "mean = ", mean(values)
print '(a, f6.3)', "std dev = ", std_dev(values)
end program main
! mean = 4.400
! std dev = 1.817x(:) 宣告是 assumed-shape 陣列,所以函式可接受任意長度的陣列。pure 承諾函式沒有副作用。格式 '(a, f6.3)' 會印出一個字串,後面接著寬度 6、小數 3 位的實數。
接下來可以學什麼
- fortran-lang.org:Learn 區有快速入門教學與最佳實務。
- GNU Fortran(gfortran)手冊,內有編譯器選項與內建程序。
- 試試 Fortran Package Manager(fpm)來建立專案,並使用 Fortran 標準函式庫(stdlib)等函式庫。
- 學習時請開啟警告與執行期檢查來編譯,例如
gfortran -Wall -fcheck=all。
常見問題
Fortran 現在還有人用嗎?
有。Fortran 仍廣泛用於科學運算與高效能運算,特別是氣候、天氣、物理與工程模擬,語言標準也仍持續更新。
.f 與 .f90 檔案有什麼差別?
依慣例,.f 與 .for 檔案使用 FORTRAN 77 的舊式固定格式排版,欄位位置很重要,而 .f90 檔案使用自由格式原始碼。.f90 副檔名用於任何現代標準的自由格式程式碼,不只是 Fortran 90。
為什麼 Fortran 很快?
這門語言是為數值運算而設計的:陣列是內建的,別名規則讓編譯器能積極最佳化,而且數十年來的編譯器開發都以它為目標。寫得好的 C 或 C++ 也能達到相近的速度。
Fortran 的陣列從 0 還是 1 開始?
預設從 1 開始,但你可以選擇任意下界,例如 real :: a(0:9) 宣告十個元素,索引從 0 到 9。