bigdecimal

Ruby BigDecimal to_s: Converting BigDecimal to String

BigDecimal’s to_s method returns a string in engineering notation by default, which tends to surprise developers who expect a plain decimal string. Knowing why this happens — and how to get the format you actually want — is worth a few minutes of attention.

Default Behavior

require 'bigdecimal'

BigDecimal("123.456").to_s
# => "0.123456e3"

The default format uses a 0. prefix with a power-of-ten exponent. This reflects BigDecimal’s internal representation, not what you would typically want to display to a user.

Getting a Plain Decimal String

Pass "F" (for fixed-point) to to_s and you get the familiar decimal format:

BigDecimal("123.456").to_s("F")
# => "123.456"

BigDecimal("0.0825").to_s("F")
# => "0.0825"

BigDecimal("1000").to_s("F")
# => "1000.0"

In practice, to_s("F") is what you will reach for whenever you need to display or serialize a BigDecimal value.

Engineering Notation with Grouping

You can also pass a digit-group width for the engineering format, which is occasionally useful for presenting large numbers with visual spacing:

BigDecimal("123456.789").to_s("3")
# => "123 456.789e0"

Formatting for Display

For display in financial applications, combine to_s("F") with Ruby’s built-in string formatting:

require 'bigdecimal'

price = BigDecimal("1234.5")
"$%.2f" % price.to_f   # fast but loses precision
# => "$1234.50"

# Safer: format after rounding to the right scale
formatted = price.round(2).to_s("F")
# => "1234.5"

sprintf("$%s", price.round(2).to_s("F"))
# => "$1234.5"

Note the trade-off: using price.to_f before the format string is faster, but it routes the value through binary floating-point and surrenders BigDecimal’s precision. For financial output, rounding first and formatting the string directly is the safer approach.

For currency display with zero-padding, round first and then split manually:

def format_currency(amount)
  rounded = amount.round(2).to_s("F")
  integer_part, decimal_part = rounded.split(".")
  decimal_part = (decimal_part || "").ljust(2, "0")[0, 2]
  "$#{integer_part}.#{decimal_part}"
end

format_currency(BigDecimal("19.9"))   # => "$19.90"
format_currency(BigDecimal("100"))    # => "$100.00"

This helper ensures you always get exactly two decimal places regardless of the source value — important when displaying prices to end users.