cv.cm

Lập trình · Ngôn ngữ lập trình

Hướng dẫn Fortran cho người mới bắt đầu

8 phút đọc

Trả lời nhanh

Fortran là ngôn ngữ biên dịch, kiểu tĩnh, được tạo ra cho tính toán số và khoa học. Fortran hiện đại (Fortran 90 và các chuẩn sau đó) có mã nguồn dạng tự do, module, mảng động và các phép toán mạnh trên cả mảng, còn trình biên dịch tạo ra mã máy rất nhanh. Hãy cài trình biên dịch gfortran miễn phí, lưu mã vào tệp .f90 rồi biên dịch bằng gfortran hello.f90 -o hello.

Fortran là gì và ai dùng nó

Fortran ("formula translation") được một nhóm do John Backus dẫn đầu phát triển tại IBM vào thập niên 1950 và là một trong những ngôn ngữ lập trình lâu đời nhất vẫn còn được dùng tích cực. Ngôn ngữ này được ISO chuẩn hóa; các bản sửa đổi lớn gồm Fortran 90, 95, 2003, 2008, 2018 và 2023. Kiểu cố định cũ (FORTRAN 77 viết hoa, mã bắt đầu ở cột 7) vẫn biên dịch được, nhưng mã mới dùng cú pháp tự do như trong bài này.

Fortran được dùng ở những nơi cần tính toán số nặng: mô hình thời tiết và khí hậu, động lực học chất lưu tính toán, mô phỏng vật lý và hóa học, cùng nhiều chương trình chạy trên siêu máy tính. Các phiên bản tham chiếu của thư viện đại số tuyến tính BLAS và LAPACK, nằm bên dưới những công cụ như NumPy và MATLAB, được viết bằng Fortran.

Cài trình biên dịch và chạy Hello, world

gfortran, một phần của GCC, là trình biên dịch miễn phí và có trên Linux (gói gfortran), macOS (Homebrew gcc) và Windows (MSYS2 hoặc WSL). ifx của Intel và LLVM Flang là các trình biên dịch khác. Trình quản lý gói Fortran (fpm) có thể tạo và build dự án.

program hello
  implicit none
  print *, "Hello, world!"
end program hello
gfortran hello.f90 -o hello
./hello
#  Hello, world!

print * dùng đầu ra theo danh sách (list-directed), tự chọn khoảng cách cho bạn (lưu ý dấu cách ở đầu). Fortran không phân biệt hoa thường: PRINT, Print và print là như nhau.

Biến và các kiểu cơ bản

Mọi khai báo nằm ở đầu một đơn vị chương trình, trước các câu lệnh thực thi. Luôn viết implicit none: nếu không, các tên chưa khai báo bắt đầu bằng chữ I đến N sẽ âm thầm là số nguyên, còn các tên khác là số thực.

KiểuVí dụ khai báoGhi chú
integerinteger :: count = 3Thường là 32 bit; dùng integer(int64) cho 64 bit
realreal :: xThường là độ chính xác đơn
real(real64)real(real64) :: yĐộ chính xác kép, lựa chọn thông dụng trong khoa học
complexcomplex :: z = (1.0, 2.0)Số phức có sẵn
logicallogical :: done = .false..true. / .false., toán tử .and., .or., .not.
charactercharacter(len=20) :: nameChuỗi độ dài cố định, được đệm thêm dấu cách
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

Hậu tố _real64 đặt kind (độ chính xác) cho một hằng số. Nếu thiếu, 1.0 là hằng độ chính xác đơn và sẽ mất chữ số khi gán cho biến độ chính xác kép.

Luồng điều khiển

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 control

do i = 1, 10 bao gồm cả hai đầu, và giá trị thứ ba tùy chọn đặt bước nhảy (do i = 10, 1, -1). cycle nhảy sang lần lặp kế tiếp và exit thoát khỏi vòng lặp. Cũng có do while (condition) và do trơn kèm exit. Chú thích bắt đầu bằng !.

Hàm, subroutine và module

Fortran có hai loại thủ tục: hàm (function) trả về một giá trị và dùng trong biểu thức; subroutine được gọi bằng call và trả kết quả qua các đối số. Hãy đặt thủ tục trong một module để trình biên dịch kiểm tra được mọi lời gọi.

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

Đối số được truyền theo tham chiếu, nên thủ tục có thể thay đổi biến của bên gọi. intent(in), intent(out) và intent(inout) ghi rõ và bắt buộc những gì mỗi đối số được phép làm. ** là toán tử lũy thừa.

Mảng: tính năng nổi bật của Fortran

Mảng là trung tâm của Fortran. Chỉ số mặc định bắt đầu từ 1 (cho phép mọi cận dưới), phép tính chạy trên cả mảng mà không cần vòng lặp, lát cắt dùng a(start:end), và nhiều hàm tích hợp thao tác trực tiếp trên mảng.

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

Mảng nhiều chiều được lưu theo thứ tự ưu tiên cột (column-major): chỉ số đầu tiên thay đổi nhanh nhất trong bộ nhớ, ngược với C. Để chạy nhanh, hãy cho vòng lặp trong cùng chạy theo chỉ số đầu tiên. Các hàm tích hợp hữu ích khác là matmul, dot_product, transpose, minval và count. Fortran hiện đại còn có vòng lặp do concurrent và coarray cho chương trình song song.

Xử lý lỗi

Fortran không có ngoại lệ. Các câu lệnh có thể thất bại nhận một đối số trạng thái: iostat cho nhập xuất, stat cho allocate. Hãy kiểm tra giá trị và xử lý; nếu bỏ qua, lỗi sẽ dừng chương trình. error stop kết thúc chương trình với một lỗi.

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

Đọc từ một biến ký tự như ở trên gọi là đọc nội bộ (internal read) và là cách thông dụng để chuyển chuỗi thành số.

Một chương trình hoàn chỉnh nhỏ: trung bình và độ lệch chuẩn

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.817

Khai báo x(:) là mảng có hình dạng suy ra (assumed-shape), nên các hàm nhận mảng có độ dài bất kỳ. pure cam kết hàm không có tác dụng phụ. Định dạng '(a, f6.3)' in một chuỗi rồi đến một số thực rộng 6 ký tự với 3 chữ số thập phân.

Học tiếp ở đâu

  • fortran-lang.org: mục Learn với bài hướng dẫn nhanh và các thực hành tốt nhất.
  • Sách hướng dẫn GNU Fortran (gfortran) về các tùy chọn biên dịch và thủ tục tích hợp.
  • Thử Trình quản lý gói Fortran (fpm) để tạo dự án và dùng các thư viện như thư viện chuẩn Fortran (stdlib).
  • Khi học, hãy biên dịch kèm cảnh báo và kiểm tra lúc chạy, ví dụ gfortran -Wall -fcheck=all.

Câu hỏi thường gặp

Ngày nay Fortran còn được dùng không?

Có. Fortran vẫn được dùng rộng rãi trong tính toán khoa học và tính toán hiệu năng cao, nhất là mô phỏng khí hậu, thời tiết, vật lý và kỹ thuật, và chuẩn ngôn ngữ vẫn đang được cập nhật.

Khác nhau giữa tệp .f và .f90 là gì?

Theo quy ước, tệp .f và .for dùng bố cục kiểu cố định cũ của FORTRAN 77, trong đó vị trí cột có ý nghĩa, còn tệp .f90 dùng mã nguồn dạng tự do. Phần mở rộng .f90 dùng cho mã dạng tự do của mọi chuẩn hiện đại, không riêng Fortran 90.

Vì sao Fortran nhanh?

Ngôn ngữ được thiết kế cho tính toán số: mảng có sẵn, quy tắc aliasing cho phép trình biên dịch tối ưu mạnh tay, và hàng chục năm phát triển trình biên dịch nhắm vào nó. C hoặc C++ viết tốt cũng có thể đạt tốc độ tương tự.

Mảng Fortran bắt đầu từ 0 hay 1?

Mặc định bắt đầu từ 1, nhưng bạn có thể chọn cận dưới bất kỳ, ví dụ real :: a(0:9) khai báo mười phần tử với chỉ số từ 0 đến 9.

Tất cả hướng dẫn lập trình