Overview
Ruby provides a comprehensive set of arithmetic operators for mathematical operations on numeric types. These operators work with integers, floats, rationals, and complex numbers, with automatic type coercion and method-based implementations that can be overridden in custom classes.
The core arithmetic operators include:
+
(addition)-
(subtraction)*
(multiplication)/
(division)%
(modulo)**
(exponentiation)
# Basic arithmetic operations
result = 10 + 5 # => 15
result = 10 - 3 # => 7
result = 4 * 6 # => 24
result = 15 / 3 # => 5
result = 17 % 5 # => 2
result = 2 ** 3 # => 8
Ruby implements these as method calls on objects, meaning 2 + 3
is syntactic sugar for 2.+(3)
. This allows classes to define their own arithmetic behavior by implementing these methods.
Basic Usage
Integer Arithmetic
Ruby handles integer arithmetic with automatic type promotion when needed:
# Integer operations
a = 15 + 25 # => 40
b = 100 - 37 # => 63
c = 12 * 8 # => 96
# Division with integers
quotient = 17 / 5 # => 3 (integer division)
remainder = 17 % 5 # => 2
# Exponentiation
power = 3 ** 4 # => 81
Float Arithmetic
Floating-point operations follow IEEE 754 standards:
# Float operations
a = 15.5 + 10.2 # => 25.7
b = 20.0 - 7.3 # => 12.7
c = 3.14 * 2.5 # => 7.85
# Float division
result = 17.0 / 5 # => 3.4
result = 17 / 5.0 # => 3.4 (mixed types)
Mixed Type Operations
Ruby automatically handles type coercion between compatible numeric types:
# Integer and float mixing
result = 10 + 3.5 # => 13.5 (promotes to float)
result = 15.7 - 5 # => 10.7 (promotes to float)
result = 3 * 2.5 # => 7.5 (promotes to float)
# Rational numbers
require 'rational'
result = 1/3r + 1/6r # => (1/2) (exact rational arithmetic)
result = 0.5 + 1/4r # => 0.75
Compound Assignment Operators
Ruby provides compound assignment operators that combine arithmetic with assignment:
x = 10
x += 5 # Equivalent to x = x + 5 # => 15
x -= 3 # Equivalent to x = x - 3 # => 12
x *= 2 # Equivalent to x = x * 2 # => 24
x /= 4 # Equivalent to x = x / 4 # => 6
x %= 5 # Equivalent to x = x % 5 # => 1
x **= 3 # Equivalent to x = x ** 3 # => 1
Operator Precedence and Associativity
Understanding operator precedence prevents unexpected results in complex expressions:
# Precedence demonstration
result = 2 + 3 * 4 # => 14, not 20 (* has higher precedence)
result = (2 + 3) * 4 # => 20 (parentheses override precedence)
# Exponentiation has highest precedence
result = 2 ** 3 ** 2 # => 512 (right-associative: 2 ** (3 ** 2))
result = (2 ** 3) ** 2 # => 64
# Mixed operations
result = 10 + 6 / 2 * 3 - 1 # => 18 (division and multiplication first)
# Evaluation: 10 + ((6 / 2) * 3) - 1 = 10 + 9 - 1 = 18
Operator Precedence (highest to lowest):
**
(exponentiation) - right associative- Unary
+
,-
(positive, negative) *
,/
,%
(multiplication, division, modulo) - left associative+
,-
(addition, subtraction) - left associative
Type Coercion and Numeric Tower
Ruby implements a numeric tower that automatically promotes types during arithmetic operations:
# Integer → Float promotion
1 + 2.0 # => 3.0 (Integer promoted to Float)
5 * 3.14 # => 15.7 (Integer promoted to Float)
# Rational arithmetic (when loaded)
require 'rational'
1/2r + 1/3r # => (5/6) (exact rational result)
1/2r + 0.5 # => 1.0 (Rational promoted to Float)
# Complex arithmetic (when loaded)
require 'complex'
3 + 2i + 1 # => (4+2i) (Integer promoted to Complex)
Custom Type Coercion
Classes can define coerce
method to participate in arithmetic operations:
class Temperature
attr_reader :celsius
def initialize(celsius)
@celsius = celsius
end
def +(other)
case other
when Numeric
Temperature.new(celsius + other)
when Temperature
Temperature.new(celsius + other.celsius)
else
x, y = other.coerce(self)
x + y
end
end
def coerce(other)
[other, celsius]
end
end
temp = Temperature.new(20)
result = temp + 5 # => Temperature with 25°C
result = 10 + temp # => 30 (via coercion)
Division Behavior
Ruby's division behavior depends on operand types and can be source of confusion:
# Integer division truncates toward negative infinity
17 / 5 # => 3
17 / -5 # => -4 (not -3)
-17 / 5 # => -4 (not -3)
-17 / -5 # => 3
# Float division preserves fractional part
17.0 / 5 # => 3.4
17 / 5.0 # => 3.4
# fdiv method always returns float
17.fdiv(5) # => 3.4
(-17).fdiv(5) # => -3.4
# div method for integer division
17.div(5) # => 3
17.0.div(5) # => 3 (returns integer even with float receiver)
Modulo Operations
The modulo operator %
in Ruby has specific behavior that differs from some other languages:
# Positive numbers
17 % 5 # => 2
23 % 7 # => 2
# Negative numbers - result has same sign as divisor
17 % -5 # => -3 (not 2)
-17 % 5 # => 3 (not -2)
-17 % -5 # => -2
# Float modulo
17.5 % 3 # => 2.5
-17.5 % 3 # => 0.5
# Checking if result follows: a == (a.div(b) * b) + (a % b)
a, b = -17, 5
a.div(b) * b + a % b # => -17 (confirms formula)
Performance Considerations
Integer vs Float Operations
Integer arithmetic is generally faster than floating-point arithmetic:
# Benchmark example (conceptual - actual timing varies)
require 'benchmark'
n = 1_000_000
Benchmark.bm do |x|
x.report("Integer") { n.times { 1000 + 2000 + 3000 } }
x.report("Float") { n.times { 1000.0 + 2000.0 + 3000.0 } }
end
Avoiding Repeated Calculations
Cache expensive calculations when possible:
# Inefficient - recalculates power in each iteration
(1..100).map { |x| x * (2 ** 10) + x }
# Efficient - calculates power once
power_of_two = 2 ** 10
(1..100).map { |x| x * power_of_two + x }
BigInteger Performance
Ruby automatically promotes integers to arbitrary precision when they exceed fixnum range:
# Small integers (Fixnum in older Ruby versions)
small = 1000 * 2000 # Fast
# Large integers (Bignum)
large = 10 ** 50 # Slower, but exact
very_large = large ** 3 # Much slower
Common Pitfalls
Integer Division Truncation
Integer division behavior can surprise developers coming from other languages:
# Common mistake: expecting float division
average = (10 + 15 + 20) / 3 # => 15, not 15.0
# Solutions:
average = (10 + 15 + 20) / 3.0 # => 15.0
average = (10 + 15 + 20).fdiv(3) # => 15.0
average = (10.0 + 15 + 20) / 3 # => 15.0
Operator Precedence Mistakes
# Mistake: forgetting precedence rules
result = 5 + 3 * 2 # => 11, not 16
result = -3 ** 2 # => -9, not 9 (unary minus has lower precedence)
# Corrections:
result = (5 + 3) * 2 # => 16
result = (-3) ** 2 # => 9
Modulo with Negative Numbers
# Unexpected results with negative numbers
-7 % 3 # => 2, not -1 (result follows divisor sign)
7 % -3 # => -2, not 1
# Use remainder method for symmetric behavior
-7.remainder(3) # => -1
7.remainder(-3) # => 1
Float Precision Issues
# Precision problems with floating-point arithmetic
0.1 + 0.2 == 0.3 # => false (precision error)
0.1 + 0.2 # => 0.30000000000000004
# Solutions:
(0.1 + 0.2).round(10) == 0.3 # => true
require 'bigdecimal'
BigDecimal('0.1') + BigDecimal('0.2') == BigDecimal('0.3') # => true
Method-Based Arithmetic
All arithmetic operators are actually method calls, enabling powerful customization:
# These are equivalent:
2 + 3 # Syntactic sugar
2.+(3) # Method call
# Can be called with send
2.send(:+, 3) # => 5
2.public_send(:*, 4) # => 8
# Dynamic operation
operation = :+
2.send(operation, 3) # => 5
Overriding Arithmetic Methods
Custom classes can define arithmetic behavior:
class Vector
attr_reader :x, :y
def initialize(x, y)
@x, @y = x, y
end
def +(other)
Vector.new(x + other.x, y + other.y)
end
def -(other)
Vector.new(x - other.x, y - other.y)
end
def *(scalar)
Vector.new(x * scalar, y * scalar)
end
def to_s
"(#{x}, #{y})"
end
end
v1 = Vector.new(3, 4)
v2 = Vector.new(1, 2)
result = v1 + v2 # => Vector(4, 6)
scaled = v1 * 3 # => Vector(9, 12)
Edge Cases and Special Values
Division by Zero
Different numeric types handle division by zero differently:
# Integer division by zero
begin
5 / 0
rescue ZeroDivisionError => e
puts e.message # "divided by 0"
end
# Float division by zero
5.0 / 0.0 # => Infinity
-5.0 / 0.0 # => -Infinity
0.0 / 0.0 # => NaN (Not a Number)
# Checking for special float values
result = 5.0 / 0.0
result.infinite? # => 1 (positive infinity)
result.finite? # => false
nan_result = 0.0 / 0.0
nan_result.nan? # => true
Very Large Numbers
Ruby handles arbitrarily large integers automatically:
# Automatic bignum promotion
factorial_20 = (1..20).inject(:*) # => Large integer
factorial_100 = (1..100).inject(:*) # => Very large integer
# No overflow - just uses more memory
huge = 10 ** 1000 # Works fine, returns exact result
# Check if number is a bignum (older Ruby versions)
1000.class # => Integer (unified in newer Ruby)
(10 ** 50).class # => Integer
Reference
Arithmetic Operators Summary
Operator | Method | Description | Example | Result |
---|---|---|---|---|
+ |
#+ |
Addition | 5 + 3 |
8 |
- |
#- |
Subtraction | 5 - 3 |
2 |
* |
#* |
Multiplication | 5 * 3 |
15 |
/ |
#/ |
Division | 15 / 3 |
5 |
% |
#% |
Modulo | 17 % 5 |
2 |
** |
#** |
Exponentiation | 2 ** 3 |
8 |
Compound Assignment Operators
Operator | Equivalent | Description |
---|---|---|
+= |
x = x + y |
Addition assignment |
-= |
x = x - y |
Subtraction assignment |
*= |
x = x * y |
Multiplication assignment |
/= |
x = x / y |
Division assignment |
%= |
x = x % y |
Modulo assignment |
**= |
x = x ** y |
Exponentiation assignment |
Related Methods
Method | Description | Example | Result |
---|---|---|---|
#fdiv(other) |
Float division | 17.fdiv(5) |
3.4 |
#div(other) |
Integer division | 17.div(5) |
3 |
#remainder(other) |
Remainder (different sign behavior than %) | (-7).remainder(3) |
-1 |
#abs |
Absolute value | (-5).abs |
5 |
#abs2 |
Absolute value squared | (-3).abs2 |
9 |
Type Promotion Rules
Operation Types | Result Type | Example |
---|---|---|
Integer + Integer | Integer | 3 + 5 → Integer |
Integer + Float | Float | 3 + 5.0 → Float |
Integer + Rational | Rational | 3 + (1/2r) → Rational |
Float + Rational | Float | 3.0 + (1/2r) → Float |
Any + Complex | Complex | 3 + (2+1i) → Complex |
Numeric Classes Hierarchy
Numeric
├── Integer
├── Float
├── Rational
└── Complex
Each class implements the arithmetic methods with appropriate behavior for their numeric type, with automatic coercion between compatible types during operations.