CrackedRuby logo

CrackedRuby

Arithmetic Operators

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):

  1. ** (exponentiation) - right associative
  2. Unary +, - (positive, negative)
  3. *, /, % (multiplication, division, modulo) - left associative
  4. +, - (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 + 5Integer
Integer + Float Float 3 + 5.0Float
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.