Program Structure
Hello, World
puts "Hello, World!" program hello
implicit none
print *, "Hello, World!"
end program hello Every Fortran program begins with
program name and ends with end program name. The print *, statement writes to standard output; the asterisk means "use default formatting." Modern Fortran programs always include implicit none immediately after the program statement.IMPLICIT NONE
name = "Alice"
age = 30
score = 98.5
puts name
puts age
puts score program variable_demo
implicit none
character(len=20) :: name
integer :: age
real :: score
name = "Alice"
age = 30
score = 98.5
print *, trim(name)
print *, age
print *, score
end program variable_demo implicit none requires every variable to be explicitly declared. Without it, Fortran automatically assigns types based on the first letter of the variable name β a legacy rule that causes hard-to-find bugs. Modern Fortran code always uses implicit none; it is the equivalent of Ruby's always-required local variable assignment before use.Comments
# Single-line comment
count = 10 # Inline comment
puts count program with_comments
implicit none
integer :: count ! Inline comment
! Standalone comment line
count = 10
print *, count ! Print the value
end program with_comments Fortran uses
! for comments, both standalone and inline. This is identical to Ruby's # character in purpose. There are no block comment delimiters β each comment line requires its own !.Line Continuation
result = 1 + 2 + 3 +
4 + 5 + 6
puts result program continuation
implicit none
real :: result
! Ampersand at the end continues to the next line
result = 1.0 + 2.0 + 3.0 + &
4.0 + 5.0 + 6.0
print *, "Sum:", result
end program continuation Fortran uses the ampersand
& at the end of a line to continue onto the next. Ruby allows method chains across lines after an operator or with a backslash. The Fortran & can also appear at the start of the continuation line when breaking inside a string literal.Internal Subprograms (CONTAINS)
def greet(name)
puts "Hello, #{name}!"
end
greet("Alice")
greet("Bob") program contains_demo
implicit none
call greet("Alice")
call greet("Bob")
contains
subroutine greet(person_name)
character(len=*), intent(in) :: person_name
print *, "Hello, " // trim(person_name) // "!"
end subroutine greet
end program contains_demo The
contains keyword separates the executable part of a program from its internal subroutines and functions. Internal subprograms can access the host program's variables directly β similar to how Ruby methods defined inside another method have closure access, but in Fortran the access is explicit and by host association.Variables & Types
INTEGER
score = 95
total = 100
puts "Score: #{score} out of #{total}" program integer_example
implicit none
integer :: score, total
score = 95
total = 100
print *, "Score:", score, "out of", total
print *, "Integer kind (bytes):", kind(score)
end program integer_example Fortran
INTEGER is 32 bits by default (range β2,147,483,648 to 2,147,483,647) on most systems. The kind() intrinsic returns the kind value, which equals the number of bytes. Multiple variables can be declared on one line separated by commas.REAL and DOUBLE PRECISION
price = 9.99 # Ruby Float is always 64-bit
precise = 3.14159265358979
puts price
puts precise program real_types
implicit none
real :: single_value ! 32-bit, ~7 decimal digits
double precision :: double_value ! 64-bit, ~15 decimal digits
single_value = 3.14159
double_value = 3.14159265358979d0 ! d0 suffix = double precision
print *, "Single:", single_value
print *, "Double:", double_value
print *, "Single kind:", kind(single_value)
print *, "Double kind:", kind(double_value)
end program real_types Fortran
real is 32-bit single precision; double precision is 64-bit, equivalent to Ruby's Float. The d0 suffix marks a double-precision literal (like Python's no-suffix float is always 64-bit). The e0 suffix marks single precision.LOGICAL (Boolean)
is_ready = true
has_errors = false
puts "Ready: #{is_ready}"
puts "OK: #{is_ready && !has_errors}" program logical_example
implicit none
logical :: is_ready, has_errors
is_ready = .true.
has_errors = .false.
print *, "Ready:", is_ready
print *, "Errors:", has_errors
print *, "Both OK:", is_ready .and. .not. has_errors
print *, "Either bad:", .not. is_ready .or. has_errors
end program logical_example Fortran
LOGICAL literals are .true. and .false. with surrounding dots. Boolean operators are spelled out: .and., .or., .not., .eqv. (exclusive equality), and .neqv.. This verbose syntax prevents ambiguity in expressions.COMPLEX Numbers
require 'complex'
voltage = Complex(120.0, 0.0)
current = Complex(2.0, -0.5)
puts "Voltage: #{voltage}"
puts "Real part: #{voltage.real}"
puts "Magnitude: #{voltage.abs.round(2)}" program complex_example
implicit none
complex :: voltage, impedance
voltage = (120.0, 0.0)
impedance = (50.0, -30.0)
print *, "Voltage:", voltage
print *, "Real part:", real(voltage)
print *, "Imaginary:", aimag(impedance)
print *, "Magnitude:", abs(impedance)
print *, "Conjugate:", conjg(impedance)
end program complex_example Fortran has built-in
COMPLEX number support as a first-class type, making it ideal for signal processing, quantum mechanics, and electrical engineering. The intrinsics real(), aimag(), abs(), and conjg() extract components, magnitude, and conjugate respectively.KIND System (Precision Control)
# Ruby Float is always 64-bit; no 32-bit float
large_int = 9_000_000_000 # Ruby Integer is arbitrary precision
puts large_int program kind_system
use iso_fortran_env, only: int8, int32, int64, real32, real64
implicit none
integer(kind=int8) :: tiny_number ! 8-bit: -128 to 127
integer(kind=int32) :: normal_number ! 32-bit
integer(kind=int64) :: large_number ! 64-bit
real(kind=real32) :: single_float ! 32-bit (~7 digits)
real(kind=real64) :: double_float ! 64-bit (~15 digits)
tiny_number = 127
normal_number = 2147483647
large_number = 9000000000_int64
single_float = 3.14_real32
double_float = 3.14159265358979_real64
print *, "int64:", large_number
print *, "real64:", double_float
end program kind_system The KIND system lets you precisely control the storage size of numeric types.
iso_fortran_env provides portable named constants like int64 and real64. The _kind suffix on a literal constant sets its kind: 3.14_real64 is a double-precision literal. This level of control is essential for scientific computing where memory layout affects performance.PARAMETER (Named Constants)
PI = 3.14159265358979
GRAVITY = 9.81
puts PI
puts GRAVITY program parameters_demo
implicit none
real, parameter :: pi = 3.14159265358979
real, parameter :: gravity = 9.81
integer, parameter :: max_iterations = 1000
real :: area, velocity
area = pi * 5.0 * 5.0 ! Circle of radius 5
velocity = gravity * 10.0 ! After 10 seconds of free fall
print *, "Pi:", pi
print *, "Gravity:", gravity
print *, "Circle area (r=5):", area
print *, "Velocity after 10s:", velocity
print *, "Max iterations:", max_iterations
end program parameters_demo The
parameter attribute declares compile-time constants that cannot be changed at runtime. Fortran's parameter is analogous to Ruby's capitalized constants. Named parameters improve readability and prevent "magic number" bugs β the compiler catches any attempt to assign a new value to a parameter.Arithmetic & Math
Basic Operators & Integer Division
numerator = 17
denominator = 5
puts numerator / denominator # => 3 (integer division)
puts numerator % denominator # => 2
puts numerator.to_f / denominator # => 3.4 program basic_arithmetic
implicit none
integer :: numerator, denominator, quotient, remainder_value
real :: decimal_result
numerator = 17
denominator = 5
quotient = numerator / denominator ! Integer division: 3
remainder_value = mod(numerator, denominator) ! Modulo: 2
decimal_result = real(numerator) / real(denominator)
print *, "Integer division:", quotient
print *, "Remainder:", remainder_value
print *, "Real division:", decimal_result
end program basic_arithmetic Like Ruby, Fortran performs integer division when both operands are integers:
17 / 5 is 3, not 3.4. The mod() intrinsic computes the remainder. To get real division, convert at least one operand with real(). This is a frequent source of bugs when porting algorithms from Ruby or Python.Exponentiation & Math Intrinsics
puts 2 ** 10 # => 1024
puts Math.sqrt(144) # => 12.0
puts Math.sin(0) # => 0.0
puts Math.log(Math::E)# => 1.0 program math_intrinsics
implicit none
real :: base_value, result_value
base_value = 2.0
result_value = base_value ** 10.0
print *, "2^10 =", result_value
print *, "sqrt(144) =", sqrt(144.0)
print *, "abs(-42) =", abs(-42)
print *, "sin(0.0) =", sin(0.0)
print *, "log(e) =", log(exp(1.0))
print *, "floor(3.7) =", floor(3.7)
print *, "ceiling(3.2) =", ceiling(3.2)
end program math_intrinsics Fortran uses
** for exponentiation, identical to Ruby. Mathematical intrinsics (sqrt, sin, cos, abs, exp, log) are built into the language β no require 'math' needed. These functions are elemental: they work on scalars and arrays alike.MIN, MAX, and Type Conversion
score_a = 88
score_b = 95
puts [score_a, score_b].max # => 95
puts [score_a, score_b].min # => 88
puts (score_a + score_b) / 2.0 # => 91.5 program min_max_example
implicit none
integer :: score_a, score_b, highest, lowest
real :: average
score_a = 88
score_b = 95
highest = max(score_a, score_b)
lowest = min(score_a, score_b)
average = real(score_a + score_b) / 2.0
print *, "Highest:", highest
print *, "Lowest:", lowest
print *, "Average:", average
print *, "Max of three:", max(88, 95, 72)
end program min_max_example The
max() and min() intrinsics accept two or more arguments. real() converts an integer to floating point for division β without it, (score_a + score_b) / 2 would perform integer division and lose the fractional part. Fortran also provides nint() (nearest integer), int(), and anint() for rounding.Scientific Math Intrinsics
puts Math.log(1000) / Math.log(10) # log base 10
puts Math.hypot(3, 4) # => 5.0
puts Math.atan2(1, 1) * 4 # => pi program scientific_math
implicit none
real, parameter :: pi = 3.14159265358979
real :: angle_deg, angle_rad
angle_deg = 45.0
angle_rad = angle_deg * pi / 180.0
print *, "log10(1000) =", log10(1000.0)
print *, "log2(8) =", log(8.0) / log(2.0)
print *, "hypot(3,4) =", sqrt(3.0**2 + 4.0**2)
print *, "sin(45 deg) =", sin(angle_rad)
print *, "atan2(1,1)*4 =", atan2(1.0, 1.0) * 4.0
print *, "sinh(1.0) =", sinh(1.0)
print *, "erf(1.0) =", erf(1.0)
end program scientific_math Fortran's standard library includes
log10(), sin(), cos(), tan(), asin(), atan2(), sinh(), cosh(), tanh(), and the error function erf() β all intrinsic, no imports required. This breadth of built-in math reflects Fortran's scientific computing heritage.Strings & Characters
CHARACTER Declaration & LEN
message = "Fortran strings have fixed length"
puts message
puts message.length
puts message.strip.length program character_basics
implicit none
character(len=50) :: message
character(len=*), parameter :: greeting = "Hello!"
message = "Fortran strings have fixed declared length"
print *, trim(message)
print *, "Declared length:", len(message)
print *, "Trimmed length:", len_trim(message)
print *, "Greeting:", greeting
print *, "Greeting length:", len(greeting)
end program character_basics Fortran
character(len=N) declares a fixed-length string of exactly N characters, right-padded with spaces when a shorter value is assigned. len() returns the declared length; len_trim() returns the length without trailing spaces β the equivalent of Ruby's .length on a stripped string. The len=* syntax lets a parameter inherit its length from its initializer.String Concatenation (//)
first_name = "Alice"
last_name = "Smith"
full_name = "#{first_name} #{last_name}"
puts full_name
puts "Hello, " + first_name + "!" program string_concat
implicit none
character(len=10) :: first_name, last_name
character(len=25) :: full_name
first_name = "Alice"
last_name = "Smith"
full_name = trim(first_name) // " " // trim(last_name)
print *, full_name
print *, "Hello, " // trim(first_name) // "!"
print *, "Length:", len_trim(full_name)
end program string_concat Fortran uses
// for string concatenation. The trim() call removes trailing spaces before concatenating β without it, the fixed-length padding would appear in the result. Fortran has no string interpolation; concatenation is the only way to build strings from variables.TRIM, ADJUSTL, ADJUSTR
text = " Hello "
puts text.strip # "Hello"
puts text.lstrip # "Hello "
puts text.rstrip # " Hello" program string_adjustment
implicit none
character(len=20) :: padded_text
character(len=20) :: name
padded_text = " Hello, World! "
name = " Alice "
! trim() removes trailing spaces only
print *, "|" // trim(padded_text) // "|"
! adjustl() left-justifies (removes leading spaces)
print *, "|" // adjustl(name) // "|"
! adjustr() right-justifies
print *, "|" // adjustr(name) // "|"
! No built-in strip() β combine adjustl + trim
print *, "|" // trim(adjustl(name)) // "|"
end program string_adjustment Fortran's
trim() removes only trailing spaces β unlike Ruby's strip which removes both ends. To strip leading spaces, use adjustl() to left-justify and then trim(). adjustr() right-justifies by moving trailing spaces to the front, useful for aligning text output.INDEX and Substrings
sentence = "The quick brown fox"
puts sentence.index("fox") # => 16
puts sentence.include?("cat") # => false
puts sentence[4, 5] # => "quick" program string_search
implicit none
character(len=50) :: sentence
integer :: position
sentence = "The quick brown fox jumps over the lazy dog"
position = index(sentence, "fox")
print *, "Position of 'fox':", position
print *, "Contains 'cat':", index(sentence, "cat") > 0
print *, "Contains 'fox':", index(sentence, "fox") > 0
! Substring extraction: string(start:end)
print *, "Chars 5 to 9:", sentence(5:9) ! "quick"
print *, "From 17:", sentence(17:19) ! "fox"
end program string_search Fortran's
index(string, substring) returns the 1-based position of the first occurrence, or 0 if not found. Substring extraction uses parenthesis notation string(start:end) with 1-based indices β syntactically similar to Ruby's string[start, length] but using inclusive end positions instead of lengths.String Comparison
word_a = "apple"
word_b = "banana"
puts word_a == word_b # => false
puts word_a < word_b # => true (alphabetical)
puts word_a <=> word_b # => -1 program string_compare
implicit none
character(len=10) :: word_a, word_b
word_a = "apple"
word_b = "banana"
print *, "Equal:", word_a == word_b
print *, "Less than:", word_a < word_b ! Alphabetical
print *, "Greater than:", word_b > word_a
! LLT/LGT/LLE/LGE: locale-independent comparison
print *, "LLT (locale-independent):", llt(word_a, word_b)
print *, "LGT:", lgt(word_b, word_a)
end program string_compare Fortran supports all standard comparison operators (
==, /=, <, >, <=, >=) for character strings, using lexicographic ordering. The llt(), lgt(), lle(), lge() intrinsics perform locale-independent ASCII comparisons, which matters when code runs on systems with non-ASCII collating sequences.Arrays
Array Declaration & 1-Based Indexing
scores = [88, 92, 75, 98, 83]
puts scores.first # => 88
puts scores.last # => 83
puts scores[2] # => 75 (0-based) program array_declaration
implicit none
integer :: scores(5)
real :: temperatures(7) ! Alternative declaration
scores = [88, 92, 75, 98, 83]
temperatures = [20.1, 22.5, 19.8, 23.4, 21.0, 18.9, 24.7]
print *, "All scores:", scores
print *, "First:", scores(1) ! 1-based β NOT scores(0)!
print *, "Third:", scores(3)
print *, "Last:", scores(5)
print *, "Array size:", size(scores)
end program array_declaration Fortran arrays are 1-based by default β the first element is always index 1, not 0. This is the single biggest indexing difference from Ruby. The
size() intrinsic returns the total number of elements. Fortran also supports custom lower bounds: integer :: years(2020:2030) creates an 11-element array indexed from 2020 to 2030.Array Constructors & Implied DO
numbers = (1..5).to_a # [1, 2, 3, 4, 5]
evens = (1..10).select(&:even?) # [2, 4, 6, 8, 10]
squares = (1..5).map { |n| n**2 } # [1, 4, 9, 16, 25]
puts numbers.inspect program array_constructors
implicit none
integer :: numbers(5), squares(5), evens(5)
integer :: index
! Array constructor with literal values
numbers = [1, 2, 3, 4, 5]
! Implied DO loop inside constructor (like a list comprehension)
squares = [(index**2, index = 1, 5)]
evens = [(index*2, index = 1, 5)]
print *, "Numbers:", numbers
print *, "Squares:", squares
print *, "Evens:", evens
end program array_constructors The implied DO syntax
[(expression, var = start, end)] is Fortran's array comprehension β similar to Ruby's map but embedded directly in the array constructor. A stride can be added: [(index, index = 1, 10, 2)] produces [1, 3, 5, 7, 9].Array Slices (Sections)
data = (2..20).step(2).to_a # [2, 4, 6, ..., 20]
puts data[0, 3].inspect # first 3
puts data[-3..].inspect # last 3
puts data.values_at(*0.step(9, 2)).inspect # every other program array_slices
implicit none
integer :: data_values(10)
integer :: index
data_values = [(index * 2, index = 1, 10)] ! [2,4,6,8,10,12,14,16,18,20]
print *, "All:", data_values
print *, "First three:", data_values(1:3)
print *, "Last three:", data_values(8:10)
print *, "Every other:", data_values(1:10:2) ! stride of 2
print *, "Reversed:", data_values(10:1:-1)
print *, "Middle:", data_values(4:7)
end program array_slices Fortran array sections use the syntax
array(start:end:stride) β syntactically similar to Python's slice notation. The stride can be negative to reverse. Array sections are themselves arrays and can be used anywhere a full array can, including as arguments and in expressions.Whole-Array Arithmetic
prices = [10.0, 25.0, 8.50, 42.0]
discounted = prices.map { |p| p * 0.9 }
doubled = prices.map { |p| p * 2 }
puts discounted.inspect program array_arithmetic
implicit none
real :: prices(4), discounted(4), tax_included(4)
prices = [10.0, 25.0, 8.50, 42.0]
! Operations apply to every element simultaneously
discounted = prices * 0.9 ! 10% discount on all
tax_included = prices * 1.08 ! Add 8% tax
print *, "Original:", prices
print *, "Discounted:", discounted
print *, "With tax:", tax_included
! Element-wise addition of two arrays
print *, "Sum of arrays:", prices + [1.0, 2.0, 3.0, 4.0]
end program array_arithmetic Fortran's most celebrated feature is whole-array arithmetic: operations on an array apply to every element simultaneously.
prices * 0.9 is equivalent to Ruby's prices.map { |p| p * 0.9 } but is a single compiler instruction that can be vectorized automatically. This is the foundation of Fortran's performance advantage in scientific computing.SUM, PRODUCT, ANY, ALL, COUNT
numbers = [3, 1, 4, 1, 5, 9]
puts numbers.sum # => 23
puts numbers.reduce(:*) # => 540
puts numbers.count { |n| n > 3 } # => 2
puts numbers.any? { |n| n > 8 } # => true
puts numbers.all? { |n| n > 0 } # => true program array_intrinsics
implicit none
integer :: numbers(6)
numbers = [3, 1, 4, 1, 5, 9]
print *, "Sum:", sum(numbers)
print *, "Product:", product(numbers)
print *, "Count > 3:", count(numbers > 3)
print *, "Any > 8:", any(numbers > 8)
print *, "All > 0:", all(numbers > 0)
print *, "All > 3:", all(numbers > 3)
end program array_intrinsics Fortran's reduction intrinsics map directly to Ruby's Enumerable methods.
sum() and product() reduce an entire array in one call. Logical tests like numbers > 3 produce a logical array (a mask); count(), any(), and all() then operate on that mask.MAXVAL, MINVAL, MAXLOC, MINLOC
data = [3.4, 7.8, 1.2, 9.5, 4.6]
puts data.max # => 9.5
puts data.min # => 1.2
puts data.each_with_index.max[1] # => 3 (0-based index) program array_extrema
implicit none
real :: measurements(5)
measurements = [3.4, 7.8, 1.2, 9.5, 4.6]
print *, "Max value:", maxval(measurements)
print *, "Min value:", minval(measurements)
print *, "Location of max:", maxloc(measurements) ! Returns rank-1 array
print *, "Location of min:", minloc(measurements) ! 1-based index!
print *, "Range:", maxval(measurements) - minval(measurements)
end program array_extrema maxloc() and minloc() return the 1-based index of the extreme value as a rank-1 integer array. The result is an array (not a scalar) to handle multidimensional arrays where the location has multiple indices. For a 1D array you can use maxloc(data, 1) to get a scalar. This is unlike Ruby's each_with_index.max which returns a pair.2D Arrays (Matrices)
matrix = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
puts matrix[0].inspect # row 0
puts matrix.map { |r| r[1] }.inspect # column 1 program matrix_example
implicit none
integer :: matrix(3, 3)
matrix(1, :) = [1, 2, 3]
matrix(2, :) = [4, 5, 6]
matrix(3, :) = [7, 8, 9]
print *, "Row 1:", matrix(1, :)
print *, "Row 2:", matrix(2, :)
print *, "Column 2:", matrix(:, 2)
print *, "Element (2,3):", matrix(2, 3)
print *, "Shape:", shape(matrix)
print *, "Size:", size(matrix)
end program matrix_example Fortran uses
array(row, col) notation for 2D arrays. Critically, Fortran stores matrices in column-major order β columns are contiguous in memory. The colon : selects all elements along a dimension, making matrix(1, :) the first row and matrix(:, 2) the second column.RESHAPE and WHERE
flat = [1, 2, 3, 4, 5, 6]
# Ruby has no built-in 2D reshape
data = [-1.5, 3.2, -0.8, 5.1]
clipped = data.map { |v| v < 0 ? 0.0 : v }
puts clipped.inspect program reshape_and_where
implicit none
integer :: flat_data(6), grid(2, 3)
real :: measurements(5), clipped(5)
flat_data = [1, 2, 3, 4, 5, 6]
grid = reshape(flat_data, [2, 3]) ! Fill column-first
print *, "Grid row 1:", grid(1, :) ! [1, 3, 5]
print *, "Grid row 2:", grid(2, :) ! [2, 4, 6]
measurements = [-1.5, 3.2, -0.8, 5.1, -2.3]
clipped = measurements
where (clipped < 0.0) clipped = 0.0 ! Conditional element assignment
print *, "Original:", measurements
print *, "Clipped:", clipped
end program reshape_and_where reshape(source, shape) rearranges array elements into a new shape, filling in column-major order. The where construct applies an operation only to elements that satisfy a condition β the equivalent of Ruby's map with a conditional, but operating on all matching elements simultaneously without a loop.Control Flow
IF / ELSE IF / ELSE
temperature = 28
if temperature > 30
puts "Hot day!"
elsif temperature > 20
puts "Warm day"
elsif temperature > 10
puts "Cool day"
else
puts "Cold day"
end program if_example
implicit none
integer :: temperature
temperature = 28
if (temperature > 30) then
print *, "Hot day!"
else if (temperature > 20) then
print *, "Warm day"
else if (temperature > 10) then
print *, "Cool day"
else
print *, "Cold day"
end if
end program if_example Fortran's
if / else if / else / end if is directly parallel to Ruby's if / elsif / else / end. The condition must be in parentheses. Fortran also supports single-line logical IF without then: if (condition) statement, but the block form with then / end if is preferred for clarity.SELECT CASE
day_number = 3
case day_number
when 1 then puts "Monday"
when 2 then puts "Tuesday"
when 3 then puts "Wednesday"
when 4, 5 then puts "Thu or Fri"
when 6..7 then puts "Weekend"
else puts "Invalid"
end program select_case_demo
implicit none
integer :: day_number
day_number = 3
select case (day_number)
case (1)
print *, "Monday"
case (2)
print *, "Tuesday"
case (3)
print *, "Wednesday"
case (4, 5)
print *, "Thursday or Friday"
case (6:7)
print *, "Weekend"
case default
print *, "Invalid day"
end select
end program select_case_demo Fortran's
select case works on integers, characters, and logicals. Case ranges use low:high syntax; multiple values use a comma list β similar to Ruby's when. Unlike Ruby's case/when with its fall-through-free design, Fortran also never falls through. SELECT CASE cannot be used on real numbers.SELECT CASE on Characters
grade = "B"
case grade
when "A" then puts "Excellent!"
when "B" then puts "Good"
when "C" then puts "Satisfactory"
when "D", "F" then puts "Needs improvement"
else puts "Unknown grade"
end program select_char_demo
implicit none
character(len=1) :: grade
grade = "B"
select case (grade)
case ("A")
print *, "Excellent!"
case ("B", "C")
print *, "Good or Satisfactory"
case ("D":"F")
print *, "Needs improvement"
case default
print *, "Unknown grade"
end select
end program select_char_demo Character-based
select case supports exact values, comma-separated lists, and character ranges using the same low:high notation as integer ranges. Ranges use ASCII order, so "A":"Z" covers all uppercase letters. This is more expressive than Ruby's case/when for character matching.Logical Operators
age = 20
has_license = true
has_insurance = true
can_drive = has_license && has_insurance && age >= 16
puts "Can drive: #{can_drive}"
puts "Not licensed: #{!has_license}" program logical_operators
implicit none
logical :: has_license, has_insurance, can_drive
integer :: age
age = 20
has_license = .true.
has_insurance = .true.
can_drive = has_license .and. has_insurance .and. age >= 16
print *, "Can drive:", can_drive
print *, "Age OK:", age >= 16 .and. age <= 100
print *, "Not licensed:", .not. has_license
print *, "Either missing:", .not. has_license .or. .not. has_insurance
end program logical_operators Fortran's logical operators
.and., .or., .not., .eqv., and .neqv. surround their names with dots. The .eqv. operator is logical equivalence (both true or both false) and .neqv. is exclusive-or. Comparison operators include both modern symbols (==, /=, <) and older forms (.eq., .ne., .lt.).Loops
DO Loop (counted)
total = 0
(1..10).each { |n| total += n }
puts "Sum 1..10: #{total}"
5.downto(1) { |n| puts n } program do_loop
implicit none
integer :: counter, running_total
running_total = 0
do counter = 1, 10
running_total = running_total + counter
end do
print *, "Sum of 1 to 10:", running_total
print *, "Countdown:"
do counter = 5, 1, -1 ! Start, stop, stride
print *, counter
end do
end program do_loop Fortran's
do counter = start, stop, stride is equivalent to Ruby's start.step(stop, stride). The loop variable is automatically incremented by the stride (default 1) after each iteration. The stride can be negative for counting down. Unlike Ruby's blocks, Fortran DO loops cannot be nested into an expression β they are pure statements.DO WHILE
value = 1
value *= 2 while value <= 100
puts "First power of 2 over 100: #{value}" program do_while_loop
implicit none
integer :: value, steps
value = 1
steps = 0
do while (value <= 100)
value = value * 2
steps = steps + 1
end do
print *, "First power of 2 exceeding 100:", value
print *, "Steps taken:", steps
! Infinite loop with explicit exit (equivalent to loop do ... end)
value = 27
do
if (mod(value, 2) == 0) then
value = value / 2
else
value = value * 3 + 1
end if
if (value == 1) exit
end do
print *, "Collatz converged to:", value
end program do_while_loop do while (condition) continues as long as the condition is true. A bare do without a condition creates an infinite loop β the equivalent of Ruby's loop do β which must be exited with exit. This pattern is common when the exit condition is complex or appears in the middle of the loop body.EXIT and CYCLE (break and next)
(1..10).each do |n|
next if n.even? # skip even
print "#{n} "
end
puts
(1..100).each do |n|
if n % 7 == 0
puts "First multiple of 7: #{n}"
break
end
end program exit_cycle_demo
implicit none
integer :: number
! CYCLE skips the rest of the loop body (like Ruby's next)
print *, "Odd numbers:"
do number = 1, 10
if (mod(number, 2) == 0) cycle
print *, number
end do
! EXIT breaks out of the loop (like Ruby's break)
print *, "First multiple of 7:"
do number = 1, 100
if (mod(number, 7) == 0) then
print *, number
exit
end if
end do
end program exit_cycle_demo cycle in Fortran is Ruby's next β it skips the remainder of the loop body and starts the next iteration. exit is Ruby's break β it leaves the enclosing loop entirely. Both work with all loop forms: counted DO, DO WHILE, and bare DO.Named Loops (Labeled DO)
# Ruby uses catch/throw for cross-level break
catch(:found) do
(1..3).each do |row|
(1..3).each do |col|
throw :found if row == 2 && col == 2
puts "(#{row},#{col})"
end
end
end program named_loops
implicit none
integer :: row, col
! Loop labels allow exit/cycle to target a specific outer loop
outer: do row = 1, 3
inner: do col = 1, 3
if (row == col) then
print *, "Diagonal at (", row, ",", col, ")"
end if
if (row == 2 .and. col == 2) exit outer ! Break the OUTER loop
end do inner
end do outer
print *, "Loops complete"
end program named_loops Fortran supports named (labeled) loops: a name before
do and after end do. exit outer breaks out of the outer loop directly, while cycle outer skips to the next outer iteration. This is cleaner than Ruby's catch/throw for breaking nested loops, and more readable than boolean flag variables.Subroutines
Subroutine Basics (CALL)
def greet(name)
puts "Hello, #{name}!"
end
greet("Alice")
greet("Bob") program subroutine_demo
implicit none
call greet("Alice")
call greet("Bob")
contains
subroutine greet(person_name)
character(len=*), intent(in) :: person_name
print *, "Hello, " // trim(person_name) // "!"
end subroutine greet
end program subroutine_demo Fortran subroutines are called with the
call keyword. They can modify their arguments in place (like pass-by-reference) and return no value β similar to a Ruby method that produces side effects. Internal subroutines placed after contains have access to the host program's variables by host association.INTENT Attributes (IN, OUT, INOUT)
# Ruby has no formal intent system; convention only
def circle_stats(radius)
pi = 3.14159265358979
area = pi * radius ** 2
circumference = 2 * pi * radius
[area, circumference] # multiple return via array
end
area, circumference = circle_stats(5.0)
puts "Area: #{area.round(2)}"
puts "Circumference: #{circumference.round(2)}" program intent_demo
implicit none
real :: radius, area, circumference
radius = 5.0
call circle_stats(radius, area, circumference)
print *, "Radius:", radius
print *, "Area:", area
print *, "Circumference:", circumference
contains
subroutine circle_stats(input_radius, area_out, circ_out)
real, intent(in) :: input_radius ! Read-only β compiler error if assigned
real, intent(out) :: area_out ! Write-only β must be set before read
real, intent(out) :: circ_out ! Write-only
real, parameter :: pi = 3.14159265358979
area_out = pi * input_radius * input_radius
circ_out = 2.0 * pi * input_radius
end subroutine circle_stats
end program intent_demo intent(in) marks a parameter as read-only β the compiler reports an error if the subroutine tries to modify it. intent(out) means the subroutine must write the value before reading it. intent(inout) allows both reading and writing. Ruby has no equivalent mechanism; intent declarations serve as both documentation and compiler-enforced contracts.INTENT(INOUT) β Modify In Place
def normalize!(numbers)
total = numbers.sum.to_f
numbers.map! { |n| n / total }
end
data = [3.0, 1.0, 4.0, 1.0, 5.0]
normalize!(data)
puts data.map { |v| v.round(3) }.inspect program inout_demo
implicit none
real :: data_values(5)
real :: normalized(5)
data_values = [3.0, 1.0, 4.0, 1.0, 5.0]
normalized = data_values
call normalize(normalized)
print *, "Original:", data_values
print *, "Normalized:", normalized
contains
subroutine normalize(array)
real, intent(inout) :: array(:) ! Assumed-shape, readable AND writable
real :: total
total = sum(array)
if (total > 0.0) array = array / total
end subroutine normalize
end program inout_demo intent(inout) declares a parameter that the subroutine both reads and modifies β analogous to Ruby's bang methods that modify their receiver in place. The (:) notation declares an assumed-shape array argument that automatically takes the shape of whatever array is passed in, without requiring the caller to pass the size separately.Array Arguments & Assumed-Shape
def stats(data)
mean = data.sum.to_f / data.size
stddev = Math.sqrt(data.map { |x| (x - mean)**2 }.sum / data.size)
[mean, stddev]
end
data = [2.5, 3.8, 1.9, 4.2, 3.1, 2.7]
mean, stddev = stats(data)
puts "Mean: #{mean.round(3)}"
puts "Std dev: #{stddev.round(3)}" program array_stats_demo
implicit none
real :: measurements(6), mean_value, std_dev
measurements = [2.5, 3.8, 1.9, 4.2, 3.1, 2.7]
call compute_stats(measurements, mean_value, std_dev)
print *, "Mean:", mean_value
print *, "Std dev:", std_dev
contains
subroutine compute_stats(data, mean_out, stddev_out)
real, intent(in) :: data(:) ! Assumed-shape array
real, intent(out) :: mean_out, stddev_out
integer :: data_size
data_size = size(data)
mean_out = sum(data) / real(data_size)
stddev_out = sqrt(sum((data - mean_out)**2) / real(data_size))
end subroutine compute_stats
end program array_stats_demo The
(:) assumed-shape notation lets a subroutine accept arrays of any size β the size is queried at runtime with size(). The expression (data - mean_out)**2 demonstrates whole-array arithmetic in a subroutine: the subtraction and squaring apply to every element simultaneously, without a loop.Functions
Function Declaration & Return
def to_fahrenheit(celsius)
celsius * 9.0 / 5.0 + 32.0
end
puts to_fahrenheit(100.0)
puts to_fahrenheit(0.0)
puts to_fahrenheit(37.0) program function_demo
implicit none
real :: celsius_temp
celsius_temp = 100.0
print *, celsius_temp, "C =", to_fahrenheit(celsius_temp), "F"
print *, "Freezing:", to_fahrenheit(0.0), "F"
print *, "Body temp:", to_fahrenheit(37.0), "F"
contains
real function to_fahrenheit(celsius)
real, intent(in) :: celsius
to_fahrenheit = celsius * 9.0 / 5.0 + 32.0
end function to_fahrenheit
end program function_demo Fortran functions assign their return value by assigning to the function name itself β
to_fahrenheit = expression. Unlike Ruby where the last expression is the return value, Fortran requires this explicit assignment. The result(name) clause provides an alternative where the return variable gets a different name from the function.RECURSIVE Functions
def factorial(n)
n <= 1 ? 1 : n * factorial(n - 1)
end
puts factorial(10)
puts factorial(0) program recursive_demo
implicit none
print *, "10! =", factorial(10)
print *, "0! =", factorial(0)
print *, "fib(10) =", fibonacci(10)
contains
recursive integer function factorial(n) result(result_value)
integer, intent(in) :: n
if (n <= 1) then
result_value = 1
else
result_value = n * factorial(n - 1)
end if
end function factorial
recursive integer function fibonacci(n) result(fib_value)
integer, intent(in) :: n
if (n <= 1) then
fib_value = n
else
fib_value = fibonacci(n - 1) + fibonacci(n - 2)
end if
end function fibonacci
end program recursive_demo Fortran requires the
recursive keyword to enable recursion β a function cannot call itself without it. The result(name) clause is required for recursive functions to avoid the ambiguity of assigning to both the function name and calling it. Ruby functions are always recursive without any special declaration.PURE Functions (No Side Effects)
# Ruby has no pure/impure distinction enforced by the compiler
# Convention: bang methods mutate; non-bang methods are "pure"
def clamp(value, minimum, maximum)
[[value, minimum].max, maximum].min
end
puts clamp(15, 0, 10) # => 10
puts clamp(-5, 0, 10) # => 0
puts clamp(7, 0, 10) # => 7 program pure_function_demo
implicit none
real :: values(5), clamped(5)
values = [-5.0, 3.0, 15.0, 7.0, -1.0]
clamped = clamp_range(values, 0.0, 10.0)
print *, "Original:", values
print *, "Clamped:", clamped
contains
! PURE: no side effects, no I/O β compiler can optimize and parallelize
pure real function clamp_scalar(value, minimum, maximum) result(clamped)
real, intent(in) :: value, minimum, maximum
clamped = min(max(value, minimum), maximum)
end function clamp_scalar
pure function clamp_range(data, minimum, maximum) result(result_data)
real, intent(in) :: data(:), minimum, maximum
real :: result_data(size(data))
result_data = min(max(data, minimum), maximum) ! Whole-array operation
end function clamp_range
end program pure_function_demo The
pure attribute declares that a function has no side effects and performs no I/O. The compiler can then safely parallelize calls, reorder them, or eliminate dead code. Pure functions can only call other pure procedures. Ruby has no equivalent compiler enforcement β purity is a convention, not a guarantee.ELEMENTAL Functions (Scalar + Array)
# In Ruby, you'd need separate scalar and array versions,
# or use map explicitly:
def to_celsius(fahrenheit)
(fahrenheit - 32.0) * 5.0 / 9.0
end
readings = [32.0, 98.6, 212.0, 72.0]
celsius = readings.map { |f| to_celsius(f) }
puts celsius.map { |v| v.round(1) }.inspect program elemental_demo
implicit none
real :: single_reading, readings(4), celsius_values(4)
! Works on a scalar
single_reading = 98.6
print *, "98.6 F =", to_celsius(single_reading), "C"
! Also works on an entire array β no map needed!
readings = [32.0, 98.6, 212.0, 72.0]
celsius_values = to_celsius(readings)
print *, "Celsius:", celsius_values
contains
elemental real function to_celsius(fahrenheit)
real, intent(in) :: fahrenheit
to_celsius = (fahrenheit - 32.0) * 5.0 / 9.0
end function to_celsius
end program elemental_demo An
elemental function is automatically both scalar and vectorized: write it once for a single value and call it with an array of any shape. The compiler applies it element-wise. This is one of Fortran's most powerful features β in Ruby you must choose between a scalar function and map.Modules
MODULE β Named Constants & Functions
module MathUtils
PI = 3.14159265358979
EULER = 2.71828182845905
def self.circle_area(radius)
PI * radius ** 2
end
end
puts MathUtils::PI
puts MathUtils.circle_area(5.0) module math_utils
implicit none
real, parameter :: pi = 3.14159265358979
real, parameter :: euler = 2.71828182845905
contains
pure real function circle_area(radius) result(area)
real, intent(in) :: radius
area = pi * radius * radius
end function circle_area
pure real function sphere_volume(radius) result(volume)
real, intent(in) :: radius
volume = (4.0 / 3.0) * pi * radius**3
end function sphere_volume
end module math_utils
program use_module
use math_utils
implicit none
print *, "Pi:", pi
print *, "Circle area (r=5):", circle_area(5.0)
print *, "Sphere volume (r=3):", sphere_volume(3.0)
end program use_module Fortran modules are the equivalent of Ruby modules: they group related constants and procedures. The
use module_name statement (like Ruby's include) brings all public module entities into the current scope. Modules can be defined in the same source file, before the program that uses them.USE β¦ ONLY (Selective Import)
require 'set'
# Ruby has no selective import; you get everything
# Namespace collision avoidance requires manual aliasing
module Geometry
PI = 3.14159265358979
def self.circle_area(r) = PI * r ** 2
def self.square_area(s) = s * s
end
area = Geometry.circle_area(3.0)
puts area.round(4) module geometry_utils
implicit none
real, parameter :: pi = 3.14159265358979
contains
pure real function circle_area(radius) result(area)
real, intent(in) :: radius
area = pi * radius * radius
end function circle_area
pure real function square_area(side) result(area)
real, intent(in) :: side
area = side * side
end function square_area
end module geometry_utils
program selective_use
! Import only what you need β square_area is excluded
use geometry_utils, only: circle_area, pi
implicit none
print *, "Pi:", pi
print *, "Circle (r=3):", circle_area(3.0)
! square_area is NOT in scope here
end program selective_use The
use module, only: name1, name2 syntax imports only the listed names. This prevents namespace pollution and makes dependencies explicit β you can immediately see what a program uses from each module. Fortran also supports renaming on import: use module, local_name => module_name.PUBLIC / PRIVATE Access Control
module BankAccount
@@balance = 0
@@transaction_count = 0 # private by convention
def self.deposit(amount)
@@balance += amount
@@transaction_count += 1
end
def self.balance = @@balance
end
BankAccount.deposit(500)
puts BankAccount.balance module bank_account
implicit none
private ! Default: all private
integer, public :: balance = 0 ! Explicitly public
integer :: transaction_count = 0 ! Private (hidden from callers)
public :: deposit, withdraw
contains
subroutine deposit(amount)
integer, intent(in) :: amount
balance = balance + amount
transaction_count = transaction_count + 1
end subroutine deposit
subroutine withdraw(amount)
integer, intent(in) :: amount
if (amount <= balance) then
balance = balance - amount
transaction_count = transaction_count + 1
end if
end subroutine withdraw
end module bank_account
program banking_demo
use bank_account
implicit none
balance = 1000
call deposit(500)
call withdraw(200)
print *, "Balance:", balance
end program banking_demo The
private statement makes all module entities private by default; public then selectively exposes specific names. This is the same encapsulation principle as Ruby's private and public method modifiers. Module variables (like transaction_count) persist between calls β they are the Fortran equivalent of class variables.Derived Types
TYPE Definition & Component Access
Person = Struct.new(:name, :age, :height)
employee = Person.new("Alice Smith", 30, 1.68)
puts employee.name
puts employee.age
puts employee.height program derived_type_demo
implicit none
type :: person
character(len=30) :: name
integer :: age
real :: height
end type person
type(person) :: employee
employee%name = "Alice Smith"
employee%age = 30
employee%height = 1.68
print *, "Name:", trim(employee%name)
print *, "Age:", employee%age
print *, "Height:", employee%height
end program derived_type_demo Fortran derived types are analogous to Ruby's
Struct β they group related fields under one name. Component access uses % instead of Ruby's .. Derived types are value types by default: assignment copies all components, unlike Ruby's reference semantics for objects.Structure Constructors
Point = Struct.new(:x, :y)
origin = Point.new(0.0, 0.0)
destination = Point.new(3.0, 4.0)
distance = Math.sqrt((destination.x - origin.x)**2 +
(destination.y - origin.y)**2)
puts "Distance: #{distance}" program type_constructor_demo
implicit none
type :: point_2d
real :: x
real :: y
end type point_2d
type(point_2d) :: origin, destination
real :: distance
origin = point_2d(0.0, 0.0) ! Structure constructor
destination = point_2d(3.0, 4.0)
distance = sqrt((destination%x - origin%x)**2 + &
(destination%y - origin%y)**2)
print *, "Origin:", origin%x, origin%y
print *, "Destination:", destination%x, destination%y
print *, "Distance:", distance
end program type_constructor_demo The structure constructor
type_name(val1, val2, ...) initializes all components in declaration order β equivalent to Ruby's Struct.new.call. Named component constructors are also possible: point_2d(x=3.0, y=4.0). Unlike Ruby, Fortran derived types do not support methods directly (that requires Fortran 2003 type-bound procedures).Arrays of Derived Types
Student = Struct.new(:name, :grade)
class_students = [
Student.new("Alice", 92.5),
Student.new("Bob", 85.0),
Student.new("Carol", 97.5),
]
avg = class_students.sum(&:grade) / class_students.size
class_students.each { |s| puts "#{s.name}: #{s.grade}" }
puts "Average: #{avg}" program type_array_demo
implicit none
type :: student
character(len=20) :: name
real :: grade
end type student
type(student) :: class_students(3)
integer :: index
real :: class_average
class_students(1) = student("Alice", 92.5)
class_students(2) = student("Bob", 85.0)
class_students(3) = student("Carol", 97.5)
! Access an array of components using %
class_average = sum(class_students(:)%grade) / 3.0
do index = 1, 3
print *, trim(class_students(index)%name), class_students(index)%grade
end do
print *, "Class average:", class_average
end program type_array_demo The syntax
array_of_types(:)%component extracts a named component from every element of an array of derived types, producing a regular array. Here, class_students(:)%grade extracts all three grades as a real array, which sum() then reduces. This avoids an explicit loop for simple reductions.Dynamic Memory
ALLOCATABLE Arrays
# Ruby arrays are always dynamic
measurements = Array.new(5) { |i| (i + 1) * 1.5 }
puts measurements.inspect
puts measurements.sum program allocatable_demo
implicit none
real, allocatable :: measurements(:)
integer :: count, index
count = 5
allocate(measurements(count)) ! Allocate at runtime
measurements = [(real(index) * 1.5, index = 1, count)]
print *, "Measurements:", measurements
print *, "Sum:", sum(measurements)
print *, "Size:", size(measurements)
deallocate(measurements) ! Release memory
print *, "Deallocated successfully"
end program allocatable_demo Allocatable arrays let you determine array size at runtime rather than compile time.
allocate(array(size)) reserves memory; deallocate(array) releases it. In modern Fortran 2003+, allocatable arrays are automatically deallocated when they go out of scope β so explicit deallocate is only needed for early release or when managing memory carefully.ALLOCATED Check & Safe Deallocation
# Ruby nil check is idiomatic
data = nil
puts data.nil? # => true
data = [1, 2, 3]
puts data.nil? # => false
data = nil # "release" (GC handles it)
puts data.nil? # => true program allocated_check_demo
implicit none
real, allocatable :: buffer(:)
integer :: requested_size
requested_size = 10
print *, "Before allocate:", allocated(buffer) ! .false.
allocate(buffer(requested_size))
buffer = 0.0
print *, "After allocate:", allocated(buffer) ! .true.
print *, "Size:", size(buffer)
if (allocated(buffer)) deallocate(buffer) ! Safe deallocation
print *, "After deallocate:", allocated(buffer) ! .false.
end program allocated_check_demo The
allocated() intrinsic tests whether an allocatable array has been allocated β analogous to Ruby's .nil? check. Calling deallocate on an unallocated array is a runtime error; wrapping it in if (allocated(array)) prevents this. Fortran 2003 also supports move_alloc to transfer ownership without copying.2D ALLOCATABLE (Dynamic Matrix)
rows, cols = 3, 4
matrix = Array.new(rows) { Array.new(cols, 0.0) }
(0...rows).each do |r|
(0...cols).each do |c|
matrix[r][c] = r + c * 0.1
end
end
puts matrix[0].inspect program dynamic_matrix_demo
implicit none
real, allocatable :: matrix(:, :)
integer :: rows, cols, row, col
rows = 3
cols = 4
allocate(matrix(rows, cols))
do col = 1, cols
do row = 1, rows
matrix(row, col) = real(row) + real(col) * 0.1
end do
end do
print *, "Row 1:", matrix(1, :)
print *, "Row 2:", matrix(2, :)
print *, "Shape:", shape(matrix)
deallocate(matrix)
end program dynamic_matrix_demo allocate(matrix(rows, cols)) creates a 2D allocatable array whose dimensions are determined at runtime. The inner loop runs over row (contiguous in memory for column-major storage) β looping over the leftmost index in the innermost loop is the cache-friendly access pattern for Fortran arrays.Input & Output
PRINT and WRITE Basics
count = 42
price = 9.99
label = "Widget"
puts "Count: #{count}"
puts "Price: #{price}"
printf("Formatted: %5d %8.2f
", count, price) program output_basics
implicit none
integer :: count
real :: price
character(len=10) :: label
count = 42
price = 9.99
label = "Widget"
print *, "Count:", count ! Default formatting
write(*, *) "Price:", price ! Equivalent to print *,
write(*, *) "Label:", trim(label)
! Formatted output using format strings
write(*, '(a, i5)') "Formatted count: ", count
write(*, '(a, f8.2)') "Formatted price: ", price
end program output_basics print *, ... and write(*, *) ... are equivalent β both write to standard output with default formatting. The first asterisk in write(unit, format) means "standard output"; the second means "default format." Fortran's format strings in write(*, '(format)') resemble printf but use descriptors like i5 (integer, width 5) and f8.2 (float, 8 wide, 2 decimal places).Formatted Output (FORMAT Descriptors)
name = "Alice"
rank = 1
percentage = 94.5
printf("%-15s %6d %8.1f
", name, rank, percentage)
printf("Avogadro: %12.4e
", 6.022e23)
printf("Score: %d
", 94) program formatted_output
implicit none
character(len=15) :: name
integer :: rank
real :: percentage
name = "Alice"
rank = 1
percentage = 94.5
! Table header and data row
write(*, '(a15, a6, a8)') "Name", "Rank", "Score"
write(*, '(a15, i6, f8.1)') trim(name), rank, percentage
! Scientific notation
write(*, '(a, es12.4)') "Avogadro: ", 6.022e23
! Zero-padded integer (no leading spaces)
write(*, '(a, i0)') "Score (exact): ", 94
! Multiple values on one line
write(*, '(3i5)') 10, 20, 30
end program formatted_output Fortran format descriptors:
i (integer), f (fixed-point real), e/es (scientific notation), a (character), g (general numeric). Width and decimal places follow: f10.3 is a real in a 10-character field with 3 decimal places. i0 is unique to Fortran β it prints the integer using exactly as many characters as needed.Internal Files (String I/O)
# Write to a string
formatted = " 42 3.142"
# Read back from a string (like parsing)
parts = formatted.strip.split
number = parts[0].to_i
float_val = parts[1].to_f
puts "Int: #{number}, Float: #{float_val}" program internal_io_demo
implicit none
character(len=30) :: data_line
integer :: number_value
real :: float_value
! Write formatted output to a string (internal file)
write(data_line, '(i5, f10.3)') 42, 3.14159
print *, "Formatted string: [" // trim(data_line) // "]"
! Read back from the string
read(data_line, '(i5, f10.3)') number_value, float_value
print *, "Integer:", number_value
print *, "Float:", float_value
! sprintf-style: build a label
write(data_line, '("Report_", i4.4, ".txt")') 2026
print *, trim(data_line)
end program internal_io_demo Fortran's "internal file" feature uses a character variable as the unit in a
write or read statement, providing an sprintf/sscanf-equivalent. This is essential for constructing formatted strings without printing them β filenames, labels, or text that will be processed further.File I/O (OPEN, READ, WRITE, CLOSE)
File.open("data.txt", "w") do |file|
(1..5).each { |i| file.puts(i * 1.5) }
end
File.readlines("data.txt").each do |line|
puts "Read: #{line.to_f}"
end program file_io_demo
implicit none
integer :: file_unit, index, io_status
real :: measurement_value
! Write to a file
open(unit=10, file="data.txt", status="replace", action="write", &
iostat=io_status)
do index = 1, 5
write(10, *) real(index) * 1.5
end do
close(10)
! Read from the file
open(unit=11, file="data.txt", status="old", action="read")
do index = 1, 5
read(11, *) measurement_value
print *, "Read:", measurement_value
end do
close(11)
end program file_io_demo Fortran file I/O uses unit numbers (integers) as file handles.
open() opens or creates a file; close() flushes and releases it. The iostat= argument captures errors as an integer (0 = success) instead of raising an exception. This example cannot run in the browser sandbox where file system access is blocked.β Gotchas for Rubyists
Arrays Are 1-Based (Not 0-Based)
data = [10, 20, 30, 40, 50]
puts data[0] # => 10 (first element)
puts data[-1] # => 50 (last element)
puts data[4] # => 50 program one_based_indexing
implicit none
integer :: values(5)
values = [10, 20, 30, 40, 50]
print *, "First:", values(1) ! 1 is first β NOT 0!
print *, "Third:", values(3)
print *, "Last:", values(5)
! Fortran allows custom lower bounds:
block
integer :: years(2020:2024)
years(2020) = 1
years(2021) = 2
years(2022) = 3
years(2023) = 4
years(2024) = 5
print *, "Year 2022 value:", years(2022)
end block
end program one_based_indexing Fortran arrays are 1-based β the first element is always index 1. This is the most disorienting difference from Ruby (0-based), Python (0-based), and JavaScript (0-based). Fortran uniquely allows any lower bound:
integer :: data(-5:5) creates an 11-element array indexed from β5 to 5. The block construct creates a new scope, like Ruby's begin/end.Column-Major Array Storage
# Ruby 2D arrays are row-major (rows are contiguous)
matrix = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
# Fastest access: iterate over columns in inner loop
matrix.each { |row| puts row.inspect } program column_major_demo
implicit none
integer :: matrix(3, 3)
! reshape fills column-by-column (column-major)
matrix = reshape([1, 2, 3, 4, 5, 6, 7, 8, 9], [3, 3])
! Result: col1=[1,2,3], col2=[4,5,6], col3=[7,8,9]
print *, "Row 1:", matrix(1, :) ! [1, 4, 7]
print *, "Row 2:", matrix(2, :) ! [2, 5, 8]
print *, "Col 1:", matrix(:, 1) ! [1, 2, 3]
! Cache-efficient loop: innermost index varies fastest (row varies)
print *, "Column-major traversal:"
print *, matrix(:, 1)
print *, matrix(:, 2)
print *, matrix(:, 3)
end program column_major_demo Fortran stores 2D arrays in column-major order β the first index (row) varies fastest in memory. This is the opposite of C, Ruby, and Python (row-major). The consequence: iterating over
matrix(:, col) in the inner loop is cache-friendly; iterating over matrix(row, :) is not. For performance-critical code, always put the row index innermost.Implicit Typing β The Silent Bug Trap
# Ruby has no implicit typing; undeclared variables raise NameError
total_score = 0
total_score += 50
total_score += 30
puts total_score # => 80 program implicit_typing_danger
implicit none ! Without this line, Fortran would assign types automatically!
integer :: total_score
! Without implicit none, a typo like "totalscore" would silently
! create a NEW variable (default REAL) instead of causing an error.
! total_score would remain 0 while you think you updated it.
total_score = 0
total_score = total_score + 50
total_score = total_score + 30
print *, "Total score:", total_score
! implicit none forces compile errors on undeclared variables
! β turning silent runtime bugs into clear compile errors
print *, "Always use implicit none!"
end program implicit_typing_danger Without
implicit none, Fortran automatically assigns types based on the first letter of a variable name: I through N β integer, everything else β real. A typo in a variable name silently creates a new variable and the original is unchanged β a notoriously hard-to-find bug. implicit none is the Fortran equivalent of Ruby's requirement that variables be assigned before use.Default REAL Is Single Precision
# Ruby Float is always 64-bit double precision
puts 0.1 + 0.2 # => 0.30000000000000004
puts (0.1 + 0.2).round(15) # shows full precision program real_precision_gotcha
use iso_fortran_env, only: real32, real64
implicit none
real(real32) :: single_answer ! Default "real" is single precision
real(real64) :: double_answer ! Like Ruby's Float
! Default real literal is single precision
single_answer = 0.1 + 0.2
double_answer = 0.1d0 + 0.2d0 ! d0 suffix = double precision
print *, "Single (0.1+0.2):", single_answer
print *, "Double (0.1+0.2):", double_answer
! Kind suffix ensures precision of literals
print *, "1.0 / 3.0 single:", 1.0_real32 / 3.0_real32
print *, "1.0 / 3.0 double:", 1.0_real64 / 3.0_real64
end program real_precision_gotcha Fortran's default
real is 32-bit single precision β only about 7 decimal digits of accuracy. This surprises Rubyists because Ruby's Float is always 64-bit. Scientific code should use real(real64) or double precision for numerical stability. Always use the d0 or _real64 suffix on double-precision literals to avoid silently losing precision.Integer Overflow (Silent Wrapping)
# Ruby Integer is arbitrary precision β no overflow ever
big = 2 ** 62
puts big # => 4611686018427387904
puts big * 4 # => 18446744073709551616 (no overflow!) program integer_overflow_demo
use iso_fortran_env, only: int32, int64
implicit none
integer(int32) :: max_32bit
integer(int64) :: large_count
max_32bit = 2147483647 ! Maximum int32 value
print *, "Max int32:", max_32bit
! Overflow wraps silently β no exception!
max_32bit = max_32bit + 1 ! Wraps to -2147483648
print *, "Overflowed int32:", max_32bit
! Use int64 for large numbers
large_count = 9000000000_int64
print *, "int64 handles it:", large_count
end program integer_overflow_demo Fortran integer arithmetic wraps on overflow without raising an error β unlike Ruby's arbitrary-precision integers that never overflow. Adding 1 to the maximum 32-bit integer gives the most negative 32-bit integer silently. For large counts, sums, or factorials, always use
integer(int64) or integer(int128) (where supported). Ruby programmers accustomed to no-overflow arithmetic must be especially vigilant.