Overview
Ruby provides native support for complex numbers through the Complex
class, enabling mathematical computations involving imaginary numbers. The Complex class represents numbers in the form a + bi
, where a
is the real part, b
is the imaginary part, and i
is the imaginary unit (√-1).
Ruby creates complex numbers using several methods: the Complex()
kernel method, string parsing with Complex()
, and mathematical operations that naturally produce complex results. Complex numbers in Ruby are immutable objects that support all standard mathematical operations including addition, subtraction, multiplication, division, and advanced functions like exponentials and trigonometry.
The implementation handles both rectangular form (a + bi) and polar form (r∠θ) representations, with automatic conversions between forms as needed. Ruby integrates complex numbers seamlessly with other numeric types, performing automatic type coercion during mixed arithmetic operations.
# Creating complex numbers
z1 = Complex(3, 4) # 3+4i
z2 = Complex("2+5i") # From string
z3 = 1 + 2i # Using the i method
# => (1+2i)
# Basic arithmetic
result = z1 + z2 # (5+9i)
product = z1 * z2 # (-14+23i)
Complex numbers serve critical roles in scientific computing, signal processing, electrical engineering simulations, and mathematical modeling. Ruby's implementation provides the foundation for building sophisticated mathematical applications requiring complex arithmetic.
Basic Usage
Creating complex numbers in Ruby offers multiple approaches depending on the source data and context. The most direct method uses the Complex()
kernel method with real and imaginary components as separate arguments.
# Direct creation with real and imaginary parts
z1 = Complex(5, 3) # 5+3i
z2 = Complex(-2, 7) # -2+7i
z3 = Complex(4) # 4+0i (pure real)
z4 = Complex(0, 2) # 0+2i (pure imaginary)
# String parsing for user input or data files
z5 = Complex("3.5+2.1i") # 3.5+2.1i
z6 = Complex("1-4i") # 1-4i
z7 = Complex("6i") # 0+6i
The Numeric#i
method provides a convenient shorthand for creating pure imaginary numbers and enables mathematical expression syntax that closely mirrors mathematical notation.
# Using the i method
pure_imaginary = 5.i # 0+5i
complex_expr = 2 + 3.i # 2+3i
decimal_complex = 1.5 + 2.7.i # 1.5+2.7i
# Mathematical operations naturally create complex numbers
sqrt_negative = Math.sqrt(-1) # (0+1i)
power_result = (-1) ** 0.5 # (0+1i)
Accessing components of complex numbers uses dedicated methods that return the real and imaginary parts as separate numeric values. These methods enable decomposition for further processing or display formatting.
z = Complex(4, -3)
real_part = z.real # 4
imag_part = z.imag # -3
# Alternative: z.imaginary
# Mathematical properties
magnitude = z.abs # 5.0 (√(4² + (-3)²))
phase = z.arg # -0.6435 (arctan(-3/4))
conjugate = z.conjugate # 4+3i
Basic arithmetic operations follow standard mathematical rules for complex numbers. Ruby handles the algebraic computations automatically, managing the distribution and combination of real and imaginary components.
a = Complex(2, 3) # 2+3i
b = Complex(1, -2) # 1-2i
sum = a + b # 3+1i
difference = a - b # 1+5i
product = a * b # 8-1i (2*1 - 3*(-2)) + (2*(-2) + 3*1)i
quotient = a / b # -0.8+1.4i
# Mixed arithmetic with other numeric types
mixed_sum = a + 5 # 7+3i
mixed_product = a * 2.5 # 5.0+7.5i
Advanced Usage
Complex numbers support advanced mathematical operations including exponentials, logarithms, and trigonometric functions. These operations extend Ruby's mathematical capabilities into the complex plane, enabling sophisticated computational applications.
# Exponential and logarithmic functions
z = Complex(1, Math::PI)
exponential = Math.exp(z) # e^(1+πi) = e * (cos(π) + i*sin(π))
natural_log = Math.log(z) # ln(1+πi)
log_base_10 = Math.log10(z) # log₁₀(1+πi)
# Trigonometric functions in complex domain
angle = Complex(0, 1)
sine = Math.sin(angle) # i*sinh(1)
cosine = Math.cos(angle) # cosh(1)
tangent = Math.tan(angle) # i*tanh(1)
Polar coordinate representation provides an alternative view of complex numbers, particularly useful for operations involving rotation and scaling. Ruby supports conversion between rectangular (a+bi) and polar (r∠θ) forms.
# Converting to polar coordinates
rectangular = Complex(3, 4)
radius = rectangular.abs # 5.0
theta = rectangular.arg # 0.927 (arctan(4/3))
# Creating from polar coordinates
polar_complex = Complex.polar(5, Math::PI/4) # 5∠45°
# => approximately 3.536+3.536i
# Rotation using polar form
original = Complex(1, 0) # Point on real axis
rotation_angle = Math::PI/2 # 90 degrees
rotated = original * Complex.polar(1, rotation_angle) # 0+1i
Power operations with complex numbers require careful handling of multi-valued functions. Ruby provides both integer and fractional power operations, with different behaviors for rational versus irrational exponents.
base = Complex(-1, 0) # -1+0i
square = base ** 2 # 1+0i
square_root = base ** 0.5 # 0+1i (principal value)
# Fractional powers with complex bases
complex_base = Complex(1, 1) # 1+1i
fractional_power = complex_base ** (1.0/3) # Cube root
# Complex exponents
result = Complex(2, 0) ** Complex(0, 1) # 2^i
Advanced computational patterns often involve sequences or arrays of complex numbers. Ruby's enumerable methods work seamlessly with complex number collections, enabling vectorized mathematical operations.
# Complex number sequences
fibonacci_complex = [1, 1+1.i, 2+1.i, 3+2.i, 5+3.i]
# Mapping operations across collections
magnitudes = fibonacci_complex.map(&:abs)
conjugates = fibonacci_complex.map(&:conjugate)
# Fourier transform component calculation
def dft_component(data, k, n)
sum = Complex(0, 0)
data.each_with_index do |value, j|
angle = -2 * Math::PI * k * j / n
twiddle = Complex.polar(1, angle)
sum += value * twiddle
end
sum
end
# Usage with complex signal data
signal = [Complex(1,0), Complex(0,1), Complex(-1,0), Complex(0,-1)]
component = dft_component(signal, 1, 4)
Matrix operations involving complex numbers require specialized handling for conjugate transposes and Hermitian properties. Ruby's matrix library integrates with complex numbers for linear algebra computations.
require 'matrix'
# Complex matrix creation
complex_matrix = Matrix[
[Complex(1,2), Complex(3,-1)],
[Complex(3,1), Complex(2,-2)]
]
# Hermitian conjugate (conjugate transpose)
hermitian = complex_matrix.conjugate.transpose
# Eigenvalue computation with complex results
eigenvalues = complex_matrix.eigenvalues # May include complex eigenvalues
Error Handling & Debugging
Complex number operations can generate several categories of errors, particularly during parsing, mathematical operations, and type conversions. Understanding these error patterns enables robust error handling in mathematical applications.
String parsing represents the most common source of complex number errors. Invalid string formats, malformed number representations, and unsupported syntax generate ArgumentError
exceptions with descriptive messages.
# Common parsing errors
begin
invalid = Complex("3 + 4j") # Wrong imaginary unit
rescue ArgumentError => e
puts e.message # "invalid value for convert(): \"3 + 4j\""
end
begin
malformed = Complex("2++3i") # Invalid syntax
rescue ArgumentError => e
puts e.message # Parsing error details
end
# Robust parsing with validation
def safe_complex_parse(input)
Complex(input.to_s.strip)
rescue ArgumentError => e
puts "Failed to parse '#{input}': #{e.message}"
Complex(0, 0) # Default fallback
end
# Usage with user input
user_input = "3.5-2.1i"
result = safe_complex_parse(user_input)
Mathematical operations can produce infinite or undefined results, particularly with division operations and certain transcendental functions. These conditions generate floating-point special values or exceptions depending on the specific operation.
# Division by zero handling
zero_complex = Complex(0, 0)
non_zero = Complex(1, 2)
begin
result = non_zero / zero_complex
# Result contains Infinity values: (Infinity+Infinity*i)
puts "Division result: #{result}"
puts "Real part finite? #{result.real.finite?}" # false
puts "Imaginary part finite? #{result.imag.finite?}" # false
rescue => e
puts "Error during division: #{e.message}"
end
# Checking for special values
def validate_complex_result(z)
real_ok = z.real.finite?
imag_ok = z.imag.finite?
unless real_ok && imag_ok
puts "Warning: Complex number contains infinite values"
puts "Real: #{z.real}, Imaginary: #{z.imag}"
return false
end
true
end
Type coercion errors occur when attempting operations between incompatible types or when complex numbers interact with objects that don't support coercion. These situations generate TypeError
exceptions.
# Type coercion with custom objects
class CustomNumber
def initialize(value)
@value = value
end
# Missing coerce method causes TypeError
end
begin
complex_num = Complex(1, 2)
custom = CustomNumber.new(5)
result = complex_num + custom # TypeError
rescue TypeError => e
puts "Type error: #{e.message}"
end
# Proper coercion implementation
class CoercibleNumber
def initialize(value)
@value = value
end
def coerce(other)
if other.is_a?(Complex)
[other, Complex(@value, 0)]
else
[@value, other]
end
end
def +(other)
Complex(@value, 0) + other
end
end
Debugging complex number computations requires specialized techniques for examining both magnitude and phase information. Standard debugging approaches may miss critical details in complex arithmetic chains.
# Complex number debugging utilities
def debug_complex(label, z)
puts "#{label}:"
puts " Rectangular: #{z}"
puts " Magnitude: #{z.abs}"
puts " Phase (rad): #{z.arg}"
puts " Phase (deg): #{z.arg * 180 / Math::PI}"
puts " Conjugate: #{z.conjugate}"
puts
end
# Usage in computation chains
z1 = Complex(3, 4)
z2 = Complex(1, -2)
debug_complex("Initial z1", z1)
debug_complex("Initial z2", z2)
result = z1 * z2
debug_complex("Product z1 * z2", result)
# Numerical precision monitoring
def precision_check(expected, actual, tolerance = 1e-10)
diff = (expected - actual).abs
if diff > tolerance
puts "Precision warning: expected #{expected}, got #{actual}"
puts "Difference: #{diff}"
end
diff <= tolerance
end
Common Pitfalls
Complex number equality comparisons can produce unexpected results due to floating-point precision issues and the nature of complex arithmetic. Ruby's equality operator performs exact comparison on both real and imaginary components, which may fail for computed results.
# Floating-point precision pitfall
a = Complex(1.0, 2.0)
b = a / 3 * 3 # Should equal a, but may not
puts a == b # May be false due to precision
puts "a: #{a}" # 1.0+2.0i
puts "b: #{b}" # 0.9999999999999999+2.0i
# Safe comparison with tolerance
def complex_equal?(z1, z2, tolerance = 1e-10)
(z1 - z2).abs < tolerance
end
puts complex_equal?(a, b) # true
The principal value convention for complex functions can cause confusion when dealing with multi-valued operations like square roots and logarithms. Ruby consistently returns principal values, but this may not match mathematical expectations in all contexts.
# Principal value surprise
negative_real = Complex(-4, 0)
sqrt_result = Math.sqrt(negative_real)
puts sqrt_result # 0.0+2.0i (principal value)
# Manual calculation might expect different branch
# All valid square roots of -4: ±2i
alternate_sqrt = -sqrt_result # 0.0-2.0i (other valid value)
# Logarithm branch cut behavior
z = Complex(-1, 0)
log_result = Math.log(z)
puts log_result # 0.0+3.141592653589793i (log(-1) = πi)
# Different branch might be expected in some contexts
expected_branch = Math.log(z) + 2 * Math::PI * 1i # Adding 2πi
String representation parsing has inconsistent behavior with different number formats and whitespace handling. The parser accepts some variations while rejecting others without clear patterns.
# Inconsistent parsing behavior
valid_formats = [
"3+4i", "3-4i", "3.5+2.7i",
"0+5i", "5i", "3"
]
problematic_formats = [
"3 + 4i", # Spaces not always supported
"3+4j", # Wrong imaginary unit
"(3,4)", # Coordinate format not supported
"3+i4" # Wrong order
]
valid_formats.each do |format|
begin
result = Complex(format)
puts "#{format} -> #{result}"
rescue ArgumentError => e
puts "#{format} -> ERROR: #{e.message}"
end
end
# Robust parsing function
def parse_complex_flexible(input)
# Handle common variations
normalized = input.to_s.gsub(/\s+/, '').gsub('j', 'i')
# Try standard parsing first
Complex(normalized)
rescue ArgumentError
# Try coordinate format: (a,b)
if match = normalized.match(/\((-?\d*\.?\d*),(-?\d*\.?\d*)\)/)
Complex(match[1].to_f, match[2].to_f)
else
raise ArgumentError, "Cannot parse '#{input}' as complex number"
end
end
Type coercion in mixed arithmetic operations follows non-obvious precedence rules that can lead to unexpected result types and precision loss.
# Mixed arithmetic precedence issues
complex_num = Complex(1, 2)
integer = 5
float = 3.5
rational = Rational(2, 3)
# Results maintain "highest" numeric type
puts (complex_num + integer).class # Complex
puts (complex_num + float).class # Complex
puts (complex_num + rational).class # Complex
# But intermediate calculations may lose precision
precise_calc = Complex(Rational(1,3), Rational(2,3))
float_calc = Complex(1/3.0, 2/3.0)
puts precise_calc # (1/3)+(2/3)*i
puts float_calc # (0.3333333333333333+0.6666666666666666i)
# Precision loss in chains
chain_precise = precise_calc * 3 # (1+2i) exactly
chain_float = float_calc * 3 # Close to (1+2i) but inexact
Comparisons between complex numbers and real numbers exhibit asymmetric behavior that violates typical comparison expectations.
# Asymmetric comparison behavior
complex_real = Complex(5, 0) # Pure real complex number
real_number = 5
puts complex_real == real_number # true
puts real_number == complex_real # true (symmetric equality works)
# But ordering comparisons fail
begin
puts complex_real > real_number # NoMethodError or false
rescue NoMethodError => e
puts "Cannot compare complex and real with >"
end
# Complex numbers don't have natural ordering
begin
[Complex(1,2), Complex(2,1), Complex(1,1)].sort
rescue ArgumentError => e
puts "Cannot sort complex numbers: #{e.message}"
end
# Safe comparison wrapper
def safe_real_compare(a, b, operator)
return nil unless a.is_a?(Numeric) && b.is_a?(Numeric)
return nil if a.is_a?(Complex) && a.imag != 0
return nil if b.is_a?(Complex) && b.imag != 0
real_a = a.is_a?(Complex) ? a.real : a
real_b = b.is_a?(Complex) ? b.real : b
real_a.send(operator, real_b)
end
Reference
Complex Class Methods
Method | Parameters | Returns | Description |
---|---|---|---|
Complex(real, imag=0) |
real (Numeric), imag (Numeric) |
Complex |
Creates complex number from real and imaginary parts |
Complex(string) |
string (String) |
Complex |
Parses string representation into complex number |
Complex.polar(abs, arg=0) |
abs (Numeric), arg (Numeric) |
Complex |
Creates complex number from polar coordinates |
Complex.rect(real, imag=0) |
real (Numeric), imag (Numeric) |
Complex |
Creates complex number from rectangular coordinates |
Complex Instance Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#real |
None | Numeric |
Returns real part of complex number |
#imag |
None | Numeric |
Returns imaginary part of complex number |
#imaginary |
None | Numeric |
Alias for #imag |
#abs |
None | Float |
Returns magnitude (absolute value) of complex number |
#magnitude |
None | Float |
Alias for #abs |
#arg |
None | Float |
Returns argument (phase angle) in radians |
#angle |
None | Float |
Alias for #arg |
#phase |
None | Float |
Alias for #arg |
#conjugate |
None | Complex |
Returns complex conjugate |
#conj |
None | Complex |
Alias for #conjugate |
#polar |
None | Array |
Returns [magnitude, phase] array |
#rect |
None | Array |
Returns [real, imaginary] array |
#rectangular |
None | Array |
Alias for #rect |
Arithmetic Operations
Method | Parameters | Returns | Description |
---|---|---|---|
#+(other) |
other (Numeric) |
Complex |
Addition with automatic coercion |
#-(other) |
other (Numeric) |
Complex |
Subtraction with automatic coercion |
#*(other) |
other (Numeric) |
Complex |
Multiplication with automatic coercion |
#/(other) |
other (Numeric) |
Complex |
Division with automatic coercion |
#**(other) |
other (Numeric) |
Complex |
Exponentiation, returns principal value |
#-@ |
None | Complex |
Unary minus (negation) |
#+@ |
None | Complex |
Unary plus (returns self) |
Comparison Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#==(other) |
other (Object) |
Boolean |
Equality comparison with automatic coercion |
#eql?(other) |
other (Object) |
Boolean |
Strict equality (same type and value) |
#hash |
None | Integer |
Hash code for use as hash key |
Conversion Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#to_c |
None | Complex |
Returns self |
#to_f |
None | Float |
Converts to float (real part only, raises if imaginary != 0) |
#to_i |
None | Integer |
Converts to integer (real part only, raises if imaginary != 0) |
#to_r |
None | Rational |
Converts to rational (real part only, raises if imaginary != 0) |
#to_s |
None | String |
String representation in rectangular form |
#inspect |
None | String |
String representation for debugging |
Mathematical Functions
Ruby's Math
module extends its functions to work with complex arguments:
Function | Complex Behavior | Domain Notes |
---|---|---|
Math.exp(z) |
e^(a+bi) = e^a * (cos(b) + i*sin(b)) |
Entire complex plane |
Math.log(z) |
Natural logarithm, principal branch | Excludes z = 0 |
Math.log10(z) |
Base-10 logarithm, principal branch | Excludes z = 0 |
Math.sqrt(z) |
Principal square root | Entire complex plane |
Math.sin(z) |
sin(a+bi) = sin(a)cosh(b) + i*cos(a)sinh(b) |
Entire complex plane |
Math.cos(z) |
cos(a+bi) = cos(a)cosh(b) - i*sin(a)sinh(b) |
Entire complex plane |
Math.tan(z) |
tan(z) = sin(z)/cos(z) |
Excludes poles |
Constants and Special Values
Constant | Value | Description |
---|---|---|
Complex::I |
0+1i |
The imaginary unit |
Complex(0,0) |
0+0i |
Complex zero |
Complex(1,0) |
1+0i |
Complex one |
Float::INFINITY |
∞ |
Can appear in real or imaginary parts |
Float::NAN |
NaN |
Not-a-Number, can appear in computations |
String Format Patterns
Pattern | Example | Result |
---|---|---|
"a+bi" |
"3+4i" |
3+4i |
"a-bi" |
"3-4i" |
3-4i |
"ai" |
"5i" |
0+5i |
"a" |
"7" |
7+0i |
"a+i" |
"2+i" |
2+1i |
"a-i" |
"2-i" |
2-1i |
Error Hierarchy
StandardError
├── ArgumentError # Invalid string parsing, invalid arguments
├── TypeError # Type coercion failures
├── ZeroDivisionError # Division by complex zero (rare)
├── NoMethodError # Comparison operations on complex numbers
└── RangeError # Domain errors in mathematical functions
Performance Characteristics
Operation Type | Complexity | Memory Usage |
---|---|---|
Creation | O(1) | Two numeric values |
Basic arithmetic | O(1) | New complex object |
Magnitude calculation | O(1) | Square root operation |
Phase calculation | O(1) | Arctangent operation |
Polar conversion | O(1) | Trigonometric functions |
String parsing | O(n) | Linear in string length |
Math functions | O(1) | Function-dependent precision |