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 |