Overview
The clamp
method constrains a numeric value to fall within a specified range by returning the value itself if within bounds, the minimum if below, or the maximum if above. Ruby implements clamp
on the Numeric
class, making it available on integers, floats, rationals, and complex numbers.
The method accepts two parameters representing the lower and upper bounds of the range. When called on a number outside this range, clamp
returns the nearest boundary value. Numbers within the range pass through unchanged.
42.clamp(10, 100)
# => 42
5.clamp(10, 100)
# => 10
150.clamp(10, 100)
# => 100
Ruby's clamp
implementation handles various numeric types and performs type coercion when necessary. The method maintains the precision and type characteristics of the original number when possible.
3.7.clamp(1.5, 5.0)
# => 3.7
(2/3r).clamp(0.5, 0.9)
# => 2/3r
The bounds must form a valid range where the minimum does not exceed the maximum. Invalid ranges raise an ArgumentError
. The comparison operations use the standard Ruby comparison operators, making clamp
compatible with any numeric type that implements proper comparison behavior.
Basic Usage
The fundamental usage pattern involves calling clamp
with two arguments representing the inclusive bounds of the acceptable range. The first argument establishes the minimum value, while the second sets the maximum.
temperature = 75
safe_temperature = temperature.clamp(32, 100)
# => 75
cold_temperature = 15
safe_temperature = cold_temperature.clamp(32, 100)
# => 32
hot_temperature = 120
safe_temperature = hot_temperature.clamp(32, 100)
# => 100
The method works with negative numbers and handles ranges that span positive and negative values. This makes it useful for centering values around zero or constraining signed measurements.
voltage = -15.5
clamped_voltage = voltage.clamp(-12.0, 12.0)
# => -12.0
position_offset = 25
centered_offset = position_offset.clamp(-50, 50)
# => 25
When working with integer values, clamp
preserves the integer type in the result. Float bounds applied to integers will return integers when the original value falls within range, but return floats when clamping occurs to a float boundary.
score = 85
final_score = score.clamp(0, 100)
# => 85 (Integer)
low_score = -5
adjusted_score = low_score.clamp(0.0, 100)
# => 0.0 (Float, clamped to float bound)
The method integrates naturally with Ruby's numeric tower, handling mixed-type operations according to Ruby's standard coercion rules. Rational and complex numbers maintain their characteristics when possible.
fraction = Rational(7, 3)
bounded_fraction = fraction.clamp(1, 3)
# => 7/3r
decimal_result = fraction.clamp(1.0, 3.0)
# => 2.3333333333333335 (converted to Float)
Advanced Usage
Complex data processing workflows often chain clamp
operations with other numeric methods to create transformation pipelines. This approach proves particularly effective when normalizing datasets or applying multiple constraints in sequence.
sensor_readings = [-15.2, 45.8, 102.3, 78.1, -5.0, 95.7]
normalized_readings = sensor_readings.map do |reading|
reading
.clamp(-10.0, 100.0) # Primary range constraint
.round(1) # Precision control
.clamp(0.0, 100.0) # Final safety bounds
end
# => [-10.0, 45.8, 100.0, 78.1, 0.0, 95.7]
Custom numeric classes that implement comparison operators work seamlessly with clamp
. This extensibility allows domain-specific value objects to benefit from range constraint functionality.
class Temperature
include Comparable
attr_reader :celsius
def initialize(celsius)
@celsius = celsius.to_f
end
def <=>(other)
celsius <=> other.celsius
end
def to_s
"#{celsius}°C"
end
end
freezing = Temperature.new(0)
boiling = Temperature.new(100)
room_temp = Temperature.new(22)
safe_temp = room_temp.clamp(freezing, boiling)
# => 22°C
Method chaining becomes powerful when combined with conditional logic and block processing. This pattern works well for data validation scenarios where different constraints apply based on context.
def process_user_input(values, mode: :strict)
bounds = mode == :strict ? [0, 100] : [-50, 150]
values.filter_map do |value|
next unless value.is_a?(Numeric)
processed = value
.abs # Remove sign
.clamp(*bounds) # Apply mode-specific bounds
.round(2) # Standardize precision
processed.zero? ? nil : processed
end
end
strict_results = process_user_input([-25, 45.678, 120, 0, 75.432])
# => [25.0, 45.68, 75.43]
lenient_results = process_user_input([-25, 45.678, 120, 0, 75.432], mode: :lenient)
# => [25.0, 45.68, 120.0, 75.43]
The method supports functional programming patterns through composition with other enumerable operations. This approach scales well for batch processing scenarios.
class DataProcessor
def self.constrain_and_aggregate(data_points, min:, max:)
clamped_values = data_points.map { |point| point.clamp(min, max) }
{
values: clamped_values,
sum: clamped_values.sum,
average: clamped_values.sum.to_f / clamped_values.length,
range: [clamped_values.min, clamped_values.max]
}
end
end
raw_data = [15, 250, 75, -10, 180, 95, 300, 45]
result = DataProcessor.constrain_and_aggregate(raw_data, min: 0, max: 200)
# => {
# values: [15, 200, 75, 0, 180, 95, 200, 45],
# sum: 810,
# average: 101.25,
# range: [0, 200]
# }
Production Patterns
Web applications frequently use clamp
for input validation and sanitization, particularly when processing user-submitted numeric data. This pattern prevents invalid ranges from propagating through application logic.
class UserPreferencesController < ApplicationController
VOLUME_RANGE = (0..100).freeze
BRIGHTNESS_RANGE = (10..100).freeze
def update
preferences = {
volume: params[:volume].to_i.clamp(VOLUME_RANGE.min, VOLUME_RANGE.max),
brightness: params[:brightness].to_i.clamp(BRIGHTNESS_RANGE.min, BRIGHTNESS_RANGE.max),
timeout: params[:timeout].to_i.clamp(30, 7200) # 30 seconds to 2 hours
}
current_user.update!(preferences)
render json: { status: :success, preferences: preferences }
end
end
Configuration management systems benefit from clamp
when loading settings from external sources where values might exceed safe operational bounds. This approach provides graceful degradation rather than system failures.
class DatabaseConfig
POOL_SIZE_BOUNDS = [1, 50].freeze
TIMEOUT_BOUNDS = [1, 300].freeze
def self.load_from_env
{
pool_size: (ENV['DB_POOL_SIZE']&.to_i || 5).clamp(*POOL_SIZE_BOUNDS),
timeout: (ENV['DB_TIMEOUT']&.to_i || 30).clamp(*TIMEOUT_BOUNDS),
max_connections: (ENV['MAX_CONNECTIONS']&.to_i || 100).clamp(10, 1000)
}
end
end
# Handles missing, invalid, or extreme environment variables gracefully
config = DatabaseConfig.load_from_env
# => { pool_size: 5, timeout: 30, max_connections: 100 }
API rate limiting implementations use clamp
to ensure request throttling stays within configured boundaries. This pattern prevents both overly permissive and overly restrictive rate limiting scenarios.
class RateLimiter
DEFAULT_REQUESTS_PER_MINUTE = 60
MIN_REQUESTS = 10
MAX_REQUESTS = 1000
def initialize(requests_per_minute = nil)
@requests_per_minute = (requests_per_minute || DEFAULT_REQUESTS_PER_MINUTE)
.clamp(MIN_REQUESTS, MAX_REQUESTS)
@window_start = Time.current
@request_count = 0
end
def allow_request?
reset_window_if_expired
return false if @request_count >= @requests_per_minute
@request_count += 1
true
end
private
def reset_window_if_expired
if Time.current - @window_start >= 60
@window_start = Time.current
@request_count = 0
end
end
end
Financial applications require precise bounds checking for monetary calculations. The clamp
method ensures transaction amounts stay within regulatory or business-defined limits.
class TransactionProcessor
DAILY_TRANSFER_LIMIT = 10_000.00
MINIMUM_TRANSFER = 0.01
def process_transfer(amount, account)
# Ensure amount stays within valid bounds
constrained_amount = amount.to_f.clamp(MINIMUM_TRANSFER, DAILY_TRANSFER_LIMIT)
if constrained_amount != amount.to_f
log_adjustment(amount, constrained_amount, account)
end
execute_transfer(constrained_amount, account)
end
private
def log_adjustment(original, adjusted, account)
Rails.logger.warn(
"Transfer amount adjusted for account #{account.id}: " \
"#{original} -> #{adjusted}"
)
end
def execute_transfer(amount, account)
# Transfer implementation
end
end
Common Pitfalls
Range boundary specification requires careful attention to parameter order. The minimum value must come first, followed by the maximum. Reversed parameters raise an ArgumentError
rather than silently producing incorrect results.
# Correct order: min, max
value = 50
result = value.clamp(10, 100) # Works correctly
# => 50
# Incorrect order raises error
begin
result = value.clamp(100, 10) # ArgumentError
rescue ArgumentError => e
puts e.message
# => "min argument must be smaller than max argument"
end
Type coercion behavior can produce unexpected results when mixing integer and float boundaries. The return type depends on both the original value's type and whether clamping occurs to a boundary of a different type.
integer_value = 5
float_min = 1.0
integer_max = 10
# Value within range: preserves original type
result = integer_value.clamp(float_min, integer_max)
# => 5 (Integer, not Float)
# Clamping to float boundary: returns float
low_value = 0
result = low_value.clamp(float_min, integer_max)
# => 1.0 (Float)
Floating-point precision issues affect boundary comparisons, particularly when using computed bounds or values derived from arithmetic operations.
calculated_value = 0.1 + 0.2 # 0.30000000000000004
expected_max = 0.3
# Precision error causes unexpected clamping
result = calculated_value.clamp(0.0, expected_max)
# => 0.3 (clamped due to precision error)
# Solution: use rational arithmetic for exact values
rational_value = Rational(1, 10) + Rational(2, 10) # 3/10r
result = rational_value.clamp(Rational(0), Rational(3, 10))
# => 3/10r (exact comparison)
Complex number constraints require understanding of Ruby's complex number comparison behavior. Complex numbers cannot be directly compared using standard comparison operators, making clamp
unsuitable for complex bounds.
complex_value = Complex(3, 4)
begin
# This will raise an ArgumentError
result = complex_value.clamp(Complex(1, 1), Complex(5, 5))
rescue ArgumentError => e
puts "Complex numbers cannot be clamped: #{e.message}"
end
# Alternative: clamp magnitude separately
magnitude = complex_value.abs # 5.0
clamped_magnitude = magnitude.clamp(1.0, 4.0) # 4.0
# Reconstruct with original phase if needed
Range validation with clamp
differs from exception-based validation approaches. While clamp
silently adjusts values, explicit validation might be more appropriate when invalid inputs should trigger error handling rather than correction.
# Silent correction approach
def set_volume_with_clamp(level)
@volume = level.clamp(0, 100)
end
# Explicit validation approach
def set_volume_with_validation(level)
unless (0..100).include?(level)
raise ArgumentError, "Volume must be between 0 and 100, got #{level}"
end
@volume = level
end
# Choose based on whether invalid input should be corrected or rejected
Reference
Method Signatures
Method | Parameters | Returns | Description |
---|---|---|---|
#clamp(min, max) |
min (Numeric), max (Numeric) |
Numeric |
Constrains receiver to fall between min and max bounds |
Parameter Requirements
Parameter | Type | Constraints | Description |
---|---|---|---|
min |
Numeric |
Must implement <=> , min <= max |
Lower bound (inclusive) |
max |
Numeric |
Must implement <=> , max >= min |
Upper bound (inclusive) |
Return Value Behavior
Condition | Return Value | Type Preservation |
---|---|---|
value < min |
min |
Returns min 's type |
min <= value <= max |
value |
Preserves original type |
value > max |
max |
Returns max 's type |
Error Conditions
Error | Condition | Message |
---|---|---|
ArgumentError |
min > max |
"min argument must be smaller than max argument" |
ArgumentError |
Complex number bounds | "Complex can't be coerced into Float" |
NoMethodError |
Non-numeric bounds | "undefined method `<=>' for [object]" |
Compatibility Matrix
Value Type | Bound Types | Result Type | Notes |
---|---|---|---|
Integer |
Integer , Integer |
Integer |
Preserves integer arithmetic |
Integer |
Float , Float |
Integer or Float |
Depends on clamping |
Float |
Integer , Integer |
Float |
Always returns float |
Rational |
Rational , Rational |
Rational |
Maintains exact precision |
Complex |
Any | ArgumentError |
Complex numbers not supported |
Common Usage Patterns
# Input validation
user_age = input.to_i.clamp(13, 120)
# Configuration bounds
pool_size = config_value.clamp(1, 50)
# Data normalization
normalized = raw_value.clamp(-1.0, 1.0)
# Safety constraints
safe_speed = requested_speed.clamp(0, speed_limit)
Performance Characteristics
- Time Complexity: O(1) - constant time operation
- Space Complexity: O(1) - no additional memory allocation
- Comparison Operations: Performs 2 comparisons maximum
- Type Coercion: May trigger coercion based on mixed types