Ruby BigDecimal to Float: Converting and What You Lose
There are legitimate reasons to convert a BigDecimal to a Float — interfacing with charting libraries, passing values to math functions, or calling APIs that expect floating-point input. However, the conversion is lossy, and understanding why helps you decide when it is safe and when it is not.
Basic Conversion
require 'bigdecimal'
n = BigDecimal("1.23456789")
n.to_f
# => 1.23456789
BigDecimal("0.1").to_f
# => 0.1 (looks fine, but the Float is not exactly 0.1)
That last example is the one to watch. The output looks correct, but the underlying Float value is not exactly 0.1 — it is the closest binary approximation, which differs in the 17th decimal place. For display purposes that is acceptable. For continued calculation, it is not.
The Precision Loss Problem
Converting to Float can introduce rounding errors:
require 'bigdecimal'
# These look equal...
BigDecimal("0.1").to_f + BigDecimal("0.2").to_f == 0.3
# => false
# The float value is not exactly 0.3
BigDecimal("0.1").to_f + BigDecimal("0.2").to_f
# => 0.30000000000000004
Once you call to_f, BigDecimal’s precision advantage is gone. The error is typically small, but it accumulates across repeated operations and can produce incorrect financial totals.
When to Convert
The decision is generally about what happens next with the value. Convert to Float when you are passing values to libraries that require Float (charting, math functions, etc.), when displaying approximate values where small errors are acceptable, or when performing CPU-intensive calculations where Float’s speed matters and precision is not critical.
require 'bigdecimal'
require 'bigdecimal/util'
price = BigDecimal("199.99")
# Acceptable: display purposes only
puts "Approx: #{price.to_f}"
# Not acceptable: continuing financial calculations with the result
total = price.to_f * 1.08 # floating-point error creeps back in
Safe Pattern: Round Before Converting
If you must convert for display, round to the required decimal places first. This way the Float you produce is as close as possible to the value you intend:
require 'bigdecimal'
amount = BigDecimal("10.005") * BigDecimal("3")
# => 30.015 exactly
# Round to 2 decimal places, then convert
amount.round(2, BigDecimal::ROUND_HALF_UP).to_f
# => 30.02
Rounding before converting minimizes the gap between your BigDecimal value and the Float representation you hand off.
Converting Float Back to BigDecimal
When you need to go the other direction — from Float back to BigDecimal — always route through a string rather than passing the Float directly:
# Wrong: captures the float's binary imprecision
BigDecimal(0.1)
# => 0.1000000000000000055511151231257827...e0
# Correct
BigDecimal("0.1")
# => 0.1e0
# Or use the util method
require 'bigdecimal/util'
0.1.to_d # => 0.1e0 (uses the string representation)
The to_d method from bigdecimal/util is a convenient shorthand that handles the string conversion for you.