CrackedRuby logo

CrackedRuby

Complex Numbers

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