From: "kstrukov (Kostiantyn Striukov)" Date: 2022-08-25T15:31:12+00:00 Subject: [ruby-core:109695] [Ruby master Feature#18979] Kernel#sprintf formatting BigDecimal without the loss of precision Issue #18979 has been reported by kstrukov (Kostiantyn Striukov). ---------------------------------------- Feature #18979: Kernel#sprintf formatting BigDecimal without the loss of precision https://bugs.ruby-lang.org/issues/18979 * Author: kstrukov (Kostiantyn Striukov) * Status: Open * Priority: Normal ---------------------------------------- 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: ```ruby 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: ```ruby 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](https://github.com/ruby/ruby/blob/cb4e2cb55a59833fc4f1f6db2f3082d1ffcafc80/sprintf.c#L794)) 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). -- https://bugs.ruby-lang.org/ Unsubscribe: