Kernel#sprintf formatting BigDecimal without the loss of precision
Recently I stumbled upon a quirk with
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
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 can be accurately formatted with
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