CrackedRuby logo

CrackedRuby

Mathematical Operations

Overview

Ruby provides comprehensive mathematical operations through multiple numeric classes and the Math module. The numeric hierarchy includes Integer, Float, Rational, and Complex classes, each with distinct behaviors and use cases. Ruby handles automatic type conversion during mathematical operations, promoting results to more precise types when necessary.

The core numeric classes inherit from Numeric, providing a unified interface for mathematical operations. Integer represents whole numbers with arbitrary precision, while Float uses double-precision IEEE 754 format. Rational maintains exact fractional representations, and Complex handles imaginary numbers.

# Numeric type hierarchy demonstration
42.class                    # => Integer
3.14.class                  # => Float
Rational(1, 3).class       # => Rational
Complex(1, 2).class        # => Complex

# Automatic type promotion
2 + 3.0                    # => 5.0 (Float)
3 / 2.0                    # => 1.5 (Float)
1 + Rational(1, 2)         # => (3/2) (Rational)

The Math module provides trigonometric, logarithmic, and other advanced mathematical functions. These functions typically work with Float values and return Float results, though they accept any Numeric input through automatic conversion.

Math.sin(Math::PI / 2)     # => 1.0
Math.log(Math::E)          # => 1.0
Math.sqrt(16)              # => 4.0

Ruby's mathematical operations handle edge cases like division by zero, overflow conditions, and special float values (infinity, NaN) according to IEEE standards. The language prioritizes mathematical correctness while maintaining performance for common operations.

Basic Usage

Ruby supports standard arithmetic operators across all numeric types. The basic operators include addition (+), subtraction (-), multiplication (*), division (/), modulo (%), and exponentiation (**). Each operator maintains type-specific behavior while supporting automatic type coercion.

# Integer operations
10 + 5                     # => 15
10 - 3                     # => 7
4 * 6                      # => 24
15 / 4                     # => 3 (integer division)
15 % 4                     # => 3 (remainder)
2 ** 10                    # => 1024

# Float operations maintain decimal precision
15.0 / 4                   # => 3.75
3.7 + 2.1                  # => 5.8
2.5 * 4                    # => 10.0

Division behavior differs between integer and floating-point operations. Integer division truncates toward negative infinity, while floating-point division preserves fractional parts. Use fdiv for floating-point division when working with integers.

15 / 4                     # => 3
15.fdiv(4)                 # => 3.75
-15 / 4                    # => -4 (truncates toward negative infinity)
-15.0 / 4                  # => -3.75

Comparison operators work across numeric types with automatic type conversion. Ruby provides standard comparison operators and specialized methods for numeric comparison.

# Cross-type comparisons
5 == 5.0                   # => true
3 < 3.1                    # => true
Rational(1, 2) > 0.4       # => true

# Specialized numeric methods
5.between?(3, 7)           # => true
(-5).abs                   # => 5
3.14.round                 # => 3
3.14.round(1)             # => 3.1

The Math module provides essential mathematical functions with consistent interfaces. Functions accept numeric arguments and return appropriate types, typically Float values.

# Trigonometric functions (angles in radians)
Math.sin(0)                # => 0.0
Math.cos(Math::PI)         # => -1.0
Math.tan(Math::PI / 4)     # => 1.0

# Logarithmic and exponential functions
Math.log(10)               # => 2.302585092994046 (natural log)
Math.log10(100)            # => 2.0
Math.exp(1)                # => 2.718281828459045

# Power and root functions
Math.sqrt(25)              # => 5.0
Math.cbrt(27)              # => 3.0
Math.hypot(3, 4)           # => 5.0 (hypotenuse)

Rational numbers provide exact fractional arithmetic, avoiding floating-point precision issues. Create Rational objects using the Rational constructor or the to_r method.

# Rational creation and operations
r1 = Rational(1, 3)        # => (1/3)
r2 = 0.25.to_r             # => (1/4)
r3 = "0.1".to_r            # => (1/10)

# Exact arithmetic
Rational(1, 3) + Rational(1, 6)  # => (1/2)
Rational(2, 3) * 3               # => (2/1)
Rational(22, 7) - Math::PI.to_r  # => (687/223)

Advanced Usage

Complex number operations enable calculations with imaginary components. Ruby's Complex class supports standard arithmetic operations and mathematical functions, maintaining both real and imaginary parts throughout calculations.

# Complex number creation and manipulation
c1 = Complex(3, 4)         # => (3+4i)
c2 = Complex.polar(5, Math::PI/4)  # => (3.5355+3.5355i)
c3 = "2+3i".to_c           # => (2+3i)

# Complex arithmetic maintains imaginary components
c1 + c2                    # => (6.536+7.536i)
c1 * Complex(1, -1)        # => (7-1i)
c1.abs                     # => 5.0 (magnitude)
c1.arg                     # => 0.927 (argument/phase)

# Mathematical functions with complex arguments
Math.sqrt(-1)              # => (0.0+1.0i)
Math.sin(Complex(1, 1))    # => (1.298+0.635i)

Numeric precision control becomes critical for financial and scientific calculations. Ruby provides multiple approaches for managing precision, including arbitrary-precision integers, rational arithmetic, and controlled rounding.

# Arbitrary precision integers
large_num = 2 ** 1000
large_num.to_s.length      # => 302 digits

# Precision-controlled calculations
require 'bigdecimal'
BigDecimal("0.1") + BigDecimal("0.2")  # => 0.3E0 (exact)

# Rational arithmetic for exact fractions
sum = (1..100).map { |n| Rational(1, n) }.sum
sum.to_f                   # => 5.187378

Method chaining enables complex mathematical expressions with readable syntax. Ruby's numeric methods return appropriate types, allowing fluent mathematical programming.

# Chained mathematical operations
result = 100.0
  .fdiv(3)
  .round(2)
  .abs
  .then { |x| Math.sqrt(x) }
  .round(4)                # => 5.7735

# Statistical calculations using method chaining
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
mean = data.sum.fdiv(data.length)
variance = data.map { |x| (x - mean) ** 2 }.sum.fdiv(data.length)
std_dev = Math.sqrt(variance)  # => 2.872

Metaprogramming techniques extend mathematical operations for domain-specific calculations. Define custom operators and mathematical behavior through method definition and operator overloading.

# Custom mathematical behavior through method definition
class Vector
  def initialize(x, y)
    @x, @y = x, y
  end
  
  def +(other)
    Vector.new(@x + other.x, @y + other.y)
  end
  
  def magnitude
    Math.hypot(@x, @y)
  end
  
  def normalize
    mag = magnitude
    Vector.new(@x / mag, @y / mag)
  end
  
  protected
  attr_reader :x, :y
end

v1 = Vector.new(3, 4)
v2 = Vector.new(1, 2)
v3 = v1 + v2               # Vector addition
v3.magnitude               # => 7.071

Type coercion protocols enable custom numeric types to integrate with Ruby's mathematical operations. Implement coerce method to define how custom types interact with built-in numerics.

# Custom numeric type with coercion support
class Percentage
  def initialize(value)
    @value = value.to_f
  end
  
  def +(other)
    case other
    when Numeric
      Percentage.new(@value + other)
    when Percentage
      Percentage.new(@value + other.value)
    end
  end
  
  def coerce(other)
    [other, @value]
  end
  
  protected
  attr_reader :value
end

pct = Percentage.new(25)
result = 100 + pct         # Uses coercion protocol

Performance & Memory

Numeric type selection significantly impacts performance and memory usage. Integer operations execute fastest, followed by Float, Rational, and Complex in order of computational overhead. Choose appropriate types based on precision requirements and performance constraints.

# Performance characteristics by type
require 'benchmark'

Benchmark.bm do |x|
  x.report("Integer")  { 1_000_000.times { 5 + 3 } }
  x.report("Float")    { 1_000_000.times { 5.0 + 3.0 } }
  x.report("Rational") { 1_000_000.times { Rational(5) + Rational(3) } }
  x.report("Complex")  { 1_000_000.times { Complex(5) + Complex(3) } }
end

# Typical results show Integer operations are fastest
# Float operations are ~2-3x slower
# Rational operations are ~10-20x slower
# Complex operations are ~15-25x slower

Memory allocation patterns differ across numeric types. Integers use immediate values for small numbers (fixnums) but allocate objects for large values. Floats always allocate objects, while Rational and Complex objects carry additional overhead for their multiple components.

# Memory usage analysis
require 'objspace'

# Small integers are immediate values (no allocation)
ObjectSpace.memsize_of(42)           # => 0
ObjectSpace.memsize_of(2**62)        # => 32 (allocated Bignum)

# Floats always allocate
ObjectSpace.memsize_of(3.14)         # => 24

# Rational objects store numerator and denominator
ObjectSpace.memsize_of(Rational(1,3)) # => 48

# Complex objects store real and imaginary parts
ObjectSpace.memsize_of(Complex(1,2))  # => 48

Floating-point arithmetic performance depends on CPU architecture and operation complexity. Basic arithmetic operations execute at hardware speed, while transcendental functions require software implementation with varying performance characteristics.

# Benchmarking mathematical functions
require 'benchmark'

Benchmark.bm do |x|
  x.report("Addition")     { 100_000.times { 3.14 + 2.71 } }
  x.report("Multiplication") { 100_000.times { 3.14 * 2.71 } }
  x.report("Division")     { 100_000.times { 3.14 / 2.71 } }
  x.report("Square root")  { 100_000.times { Math.sqrt(3.14) } }
  x.report("Trigonometry") { 100_000.times { Math.sin(3.14) } }
  x.report("Logarithm")    { 100_000.times { Math.log(3.14) } }
end

# Results typically show:
# Basic arithmetic: ~10-50 microseconds
# Square root: ~100-200 microseconds
# Transcendental functions: ~200-500 microseconds

Cache-friendly mathematical operations improve performance in tight loops. Minimize object allocation and leverage CPU cache locality for better throughput in numerical computations.

# Cache-friendly mathematical processing
def efficient_calculation(data)
  # Pre-allocate results array
  results = Array.new(data.length)
  
  # Single pass with minimal allocations
  data.each_with_index do |value, index|
    # Avoid creating intermediate objects
    temp = value * 2.0
    temp += 1.0
    results[index] = Math.sqrt(temp)
  end
  
  results
end

# Versus allocation-heavy approach
def inefficient_calculation(data)
  data.map { |value| Math.sqrt((value * 2.0) + 1.0) }
end

Large-scale mathematical computations benefit from streaming approaches that minimize memory footprint. Process data in chunks rather than loading entire datasets into memory.

# Memory-efficient statistical calculations
def streaming_statistics(data_source)
  count = 0
  sum = 0.0
  sum_squares = 0.0
  
  data_source.each do |value|
    count += 1
    sum += value
    sum_squares += value * value
  end
  
  mean = sum / count
  variance = (sum_squares / count) - (mean * mean)
  
  { mean: mean, variance: variance, std_dev: Math.sqrt(variance) }
end

# Usage with large datasets
File.foreach("large_dataset.txt") do |line|
  # Process one line at a time without loading entire file
end

Common Pitfalls

Floating-point precision errors cause the most frequent mathematical programming mistakes. IEEE 754 double-precision floats cannot exactly represent many decimal values, leading to accumulation errors in repeated calculations.

# Classic floating-point precision issues
0.1 + 0.2                  # => 0.30000000000000004 (not 0.3)
1.0 - 0.9                  # => 0.09999999999999998 (not 0.1)

# Accumulation errors in loops
sum = 0.0
1000.times { sum += 0.1 }
sum                        # => 99.99999999999986 (not 100.0)

# Solutions using Rational arithmetic
sum = Rational(0)
1000.times { sum += Rational(1, 10) }
sum.to_f                   # => 100.0 (exact)

# Or BigDecimal for decimal precision
require 'bigdecimal'
sum = BigDecimal('0')
1000.times { sum += BigDecimal('0.1') }
sum.to_f                   # => 100.0 (exact)

Division by zero behavior differs between integer and floating-point operations. Integer division raises ZeroDivisionError, while floating-point division returns infinity or NaN according to IEEE standards.

# Integer division by zero raises exception
begin
  5 / 0
rescue ZeroDivisionError => e
  puts "Integer division by zero: #{e.message}"
end

# Floating-point division by zero returns infinity
5.0 / 0.0                  # => Infinity
-5.0 / 0.0                 # => -Infinity
0.0 / 0.0                  # => NaN

# Check for special values
result = 5.0 / 0.0
result.infinite?           # => 1 (positive infinity)
result.finite?             # => false
result.nan?                # => false

Type coercion surprises occur when mixing numeric types in expressions. Ruby promotes operands to the most precise type, which may not match developer expectations.

# Unexpected type promotion
result = 5 / 2             # => 2 (integer division)
result = 5 / 2.0           # => 2.5 (float division)

# Rational arithmetic changes expression types
result = 1 + Rational(1, 2)     # => (3/2) (Rational, not Float)
result = 1 + 0.5                # => 1.5 (Float)

# Complex arithmetic propagates through calculations
result = Math.sqrt(-1) + 2      # => (2.0+1.0i) (Complex)
result.real                     # => 2.0
result.imag                     # => 1.0

Modulo operation behavior with negative numbers follows mathematical convention but surprises developers familiar with other languages. Ruby's modulo returns values with the same sign as the divisor.

# Ruby's modulo behavior
7 % 3                      # => 1
-7 % 3                     # => 2 (not -1)
7 % -3                     # => -2 (not 1)
-7 % -3                    # => -1

# Comparison with remainder operation
7.divmod(3)                # => [2, 1]
-7.divmod(3)               # => [-3, 2]
7.divmod(-3)               # => [-3, -2]
-7.divmod(-3)              # => [2, -1]

# Alternative: use remainder for C-style behavior
-7.remainder(3)            # => -1 (sign follows dividend)

Mathematical function domain errors occur when arguments fall outside valid ranges. Math module functions may raise domain errors or return NaN depending on the specific function and argument values.

# Domain errors with mathematical functions
begin
  Math.log(-1)             # Raises Math::DomainError
rescue Math::DomainError => e
  puts "Domain error: #{e.message}"
end

# Some functions return NaN instead of raising
Math.acos(2)               # => NaN (valid input range is [-1, 1])
Math.sqrt(-1)              # => (0.0+1.0i) (returns Complex)

# Check for NaN results
result = Math.acos(2)
result.nan?                # => true

# Validate input ranges
def safe_acos(x)
  return Math.acos(x) if x.between?(-1, 1)
  raise ArgumentError, "Input #{x} outside valid range [-1, 1]"
end

Reference

Numeric Classes

Class Purpose Range Precision
Integer Whole numbers Arbitrary precision Exact
Float Decimal numbers IEEE 754 double ~15 decimal digits
Rational Exact fractions Arbitrary precision Exact
Complex Imaginary numbers Component-dependent Component-dependent

Arithmetic Operators

Operator Method Description Example
+ #+ Addition 5 + 3 # => 8
- #- Subtraction 5 - 3 # => 2
* #* Multiplication 5 * 3 # => 15
/ #/ Division 5 / 2 # => 2
% #% Modulo 5 % 3 # => 2
** #** Exponentiation 2 ** 3 # => 8

Comparison Methods

Method Parameters Returns Description
#==(other) other (Numeric) Boolean Equality comparison
#<(other) other (Numeric) Boolean Less than comparison
#>(other) other (Numeric) Boolean Greater than comparison
#<=(other) other (Numeric) Boolean Less than or equal
#>=(other) other (Numeric) Boolean Greater than or equal
#<=>(other) other (Numeric) Integer Comparison operator
#between?(min, max) min, max (Numeric) Boolean Range check

Numeric Conversion Methods

Method Parameters Returns Description
#to_i None Integer Convert to integer (truncate)
#to_f None Float Convert to float
#to_r None Rational Convert to rational
#to_c None Complex Convert to complex
#round(digits=0) digits (Integer) Numeric Round to specified digits
#ceil None Integer Round up to integer
#floor None Integer Round down to integer
#truncate None Integer Remove fractional part

Math Module Functions

Function Parameters Returns Description
Math.sin(x) x (Numeric, radians) Float Sine function
Math.cos(x) x (Numeric, radians) Float Cosine function
Math.tan(x) x (Numeric, radians) Float Tangent function
Math.asin(x) x (Numeric, -1..1) Float Arcsine function
Math.acos(x) x (Numeric, -1..1) Float Arccosine function
Math.atan(x) x (Numeric) Float Arctangent function
Math.atan2(y, x) y, x (Numeric) Float Two-argument arctangent
Math.log(x) x (Numeric, >0) Float Natural logarithm
Math.log10(x) x (Numeric, >0) Float Base-10 logarithm
Math.log2(x) x (Numeric, >0) Float Base-2 logarithm
Math.exp(x) x (Numeric) Float Exponential function
Math.sqrt(x) x (Numeric, ≥0) Float/Complex Square root
Math.cbrt(x) x (Numeric) Float Cube root
Math.hypot(x, y) x, y (Numeric) Float Hypotenuse calculation

Mathematical Constants

Constant Value Description
Math::PI 3.141592653589793 Pi (π)
Math::E 2.718281828459045 Euler's number (e)

Special Float Values

Value Method Check Description
Float::INFINITY .infinite? Positive infinity
-Float::INFINITY .infinite? Negative infinity
Float::NAN .nan? Not a Number

Complex Number Methods

Method Parameters Returns Description
Complex.rect(real, imag) real, imag (Numeric) Complex Rectangular form constructor
Complex.polar(magnitude, angle) magnitude, angle (Numeric) Complex Polar form constructor
#real None Numeric Real part
#imag None Numeric Imaginary part
#abs None Float Magnitude/absolute value
#arg None Float Argument/phase angle
#conj None Complex Complex conjugate

Rational Number Methods

Method Parameters Returns Description
Rational(num, den=1) num, den (Integer) Rational Constructor
#numerator None Integer Numerator value
#denominator None Integer Denominator value
#to_f None Float Convert to floating-point

Error Classes

Exception Condition Description
ZeroDivisionError Division by zero Raised for integer division by zero
Math::DomainError Invalid domain Raised for math functions with invalid arguments
FloatDomainError Float domain error Raised for invalid float operations