Feature #18979


Kernel#sprintf formatting BigDecimal without the loss of precision

Added by kstrukov (Kostiantyn Striukov) 3 months ago.

Target version:


Recently I stumbled upon a quirk with Kernel#sprintf's f modifier behavior with BigDecimal. It seems to be not documented neither in Kernel documentation nor in BegDecimal one. Also, I didn't find previous discussions about it here.

The "problem" is that formatting BigDecimals with sprintf with f field type leads to BigDecimal implicit conversion to a float, and to the loss of precision as the result:

require "bigdecimal"

b3 = BigDecimal(1).div(BigDecimal(3), 3) #=> 0.333e0
b50 = BigDecimal(1).div(BigDecimal(3), 50) #=> 0.33333333333333333333333333333333333333333333333333e0

sprintf "%.50f" % b3 #=> "0.33300000000000001820765760385256726294755935668945"
sprintf "%.50f" % b50 #=> "0.33333333333333331482961625624739099293947219848633"

What makes this arguably even more counter-intuitive is the fact that the same BigDecimal, converted to Rational before formatting, is being handled accurately:

sprintf "%.50f" % b3.to_r #=> "0.33300000000000000000000000000000000000000000000000"
sprintf "%.50f" % b50.to_r #=> "0.33333333333333333333333333333333333333333333333333"

I understand that Rational is part of the core and BigDecimal is not, I see the difference) and know that the former has it's own API for formatting that preserves the precision. Also, from the architecture point of view the idea that some core feature should be aware of a certain part of the "outer layer" (even if it is a standard library) sounds controversial if not nonsense.

But still, having said that, from the Ruby developer perspective (who in most cases finds stdlib playing nicely with the core), the fact that BigDecimal can be accurately converted to Rational, Rational can be accurately formatted with sprintf, but BigDecimal cannot be accurately formatted using sprintf is quite a subtle thing.

For the sake of the principle of least surprise, it would be really nice if Kernel#sprintf could handle BigDecimal on par with Rational. If we cannot have this "fixed" (or shouldn't have, from the architecture standpoint or for any other reason), I think this subtlety probably should be explicitly explained in the documentation (at minimum for BigDecimal, but maybe for both).

No data to display


Also available in: Atom PDF