Ruby BigDecimal Round: Rounding Modes and Examples
Rounding has more moving parts than it first appears — especially in financial code, where choosing the wrong rounding mode can cause systematic bias across thousands of transactions. BigDecimal gives you precise control over how values are rounded through a rich set of rounding modes.
Basic Rounding
require 'bigdecimal'
n = BigDecimal("2.5678")
n.round(2) # => 0.257e1 (rounds to 2 decimal places)
n.round(2).to_s("F") # => "2.57"
n.round(0) # => 0.3e1
n.round # => 3 (returns Integer when no argument given)
When you call round with no argument, BigDecimal returns a plain Ruby Integer rather than another BigDecimal. That behavior can catch you off guard if you chain further BigDecimal operations on the result.
Rounding Modes
BigDecimal supports all IEEE rounding modes via constants on the BigDecimal module:
n = BigDecimal("2.5")
# ROUND_UP — away from zero
n.round(0, BigDecimal::ROUND_UP) # => 3
# ROUND_DOWN — toward zero (truncate)
n.round(0, BigDecimal::ROUND_DOWN) # => 2
# ROUND_HALF_UP — standard rounding (0.5 rounds up)
n.round(0, BigDecimal::ROUND_HALF_UP) # => 3
# ROUND_HALF_DOWN — 0.5 rounds down
n.round(0, BigDecimal::ROUND_HALF_DOWN) # => 2
# ROUND_HALF_EVEN — banker's rounding (0.5 rounds to nearest even)
BigDecimal("2.5").round(0, BigDecimal::ROUND_HALF_EVEN) # => 2
BigDecimal("3.5").round(0, BigDecimal::ROUND_HALF_EVEN) # => 4
# ROUND_CEILING — toward positive infinity
BigDecimal("-2.1").round(0, BigDecimal::ROUND_CEILING) # => -2
# ROUND_FLOOR — toward negative infinity
BigDecimal("-2.1").round(0, BigDecimal::ROUND_FLOOR) # => -3
You might wonder which mode to choose. ROUND_HALF_UP is the one most developers have in mind when they think of “normal” rounding. ROUND_HALF_EVEN (banker’s rounding) is worth considering when you are aggregating many values, because it avoids the systematic upward bias that ROUND_HALF_UP introduces over large datasets.
Financial Rounding
For financial applications, ROUND_HALF_UP is typically the most appropriate choice — it matches the rounding rules most users expect and is what many accounting regulations specify:
require 'bigdecimal'
require 'bigdecimal/util'
def round_currency(amount)
BigDecimal(amount.to_s).round(2, BigDecimal::ROUND_HALF_UP)
end
round_currency("10.005") # => 0.1001e2 (i.e., 10.01)
round_currency("10.004") # => 0.1e2 (i.e., 10.00)
Truncation Without Rounding
At times you need to drop digits entirely rather than round them. truncate cuts off digits without any rounding adjustment, which is useful when you need a conservative floor:
BigDecimal("9.999").truncate(2).to_s("F") # => "9.99"
BigDecimal("-9.999").truncate(2).to_s("F") # => "-9.99"
Ceiling and Floor
BigDecimal("2.1").ceil # => 3
BigDecimal("2.9").floor # => 2
BigDecimal("2.123").ceil(2) # => 0.213e1 (2.13)
BigDecimal("2.129").floor(2) # => 0.212e1 (2.12)
Both ceil and floor accept a decimal-place argument, which makes them useful when you need directional rounding at a specific precision rather than to the nearest integer.