Ruby.CodeCompared.To/Fortran

An interactive executable cheatsheet for Rubyists learning Fortran

Ruby 4.0 Fortran 2018 (GCC)
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.