gnc_numeric: fractional displays and rounding

Charles Day cedayiv at gmail.com
Sat Jul 26 13:46:57 EDT 2008


On Fri, Jul 25, 2008 at 10:07 AM, Christian Stimming <stimming at tuhh.de>wrote:

> Am Freitag, 25. Juli 2008 20:15 schrieb Charles Day:
> > So my first question: Is there an existing routine which will take a
> > gnc_numeric and attempt to change its denominator to any exact power of
> > ten?
>
> No, there's not. Feel free to add one.
>
> Also, the whole issue with rational numbers was probably an overkill
> solution
> anyway. Of course binary floating point numbers (float/double) are the
> wrong
> thing to do in financial calculations, but rational numbers are providing
> more flexibility than needed. What should have been implemented at the time
> are decimal (!) floating point numbers. I.e. instead of two large integers
> as
> nominator and denominator (the current rational numbers), we should have
> used
> one large integer for the mantissa m and a small integer e for the position
> of the decimal point, so that the resulting number is m * 10^(e)  .  This
> is
> different from "normal" floating point types which are always using a power
> of two because of the machine's number representation in binary. But
> whatever. Rational numbers is what we have right now, so that's what we
> will
> stick with.
>
> > The second thing I wanted to ask about is rounding. Currently many prices
> > going into the price db experience rounding, and this causes bugs (such
> as
> > bug 309863.)  Does anyone know WHY prices are being rounded? I suspect
> that
> > this rounding is unnecessary and is being done just to avoid fractional
> > display (see above).
>
> No idea.
>
> > If there were a routine that converted denominators to any power of ten,
> > but only without rounding, then the price editor could use that on prices
> > to make as many as possible stored in a way that prints in decimal form.
>
> As prices from actual transactions involve the division of numbers, I'm
> afraid
> the requirement "without rounding" might not be feasible in that case
> anymore. There might be rounding with much more precision than what
> currently
> happens, though, so I completely agree there is much room for improvement
> here.
>
> > Alternatively, the gnc_numeric printing routines such as
> xaccPrintAmount()
> > use this conversion purely for printing purposes; this would avoid all
> > unnecessary fractional display of gnc_numerics with only a small
> > performance hit (I say "small" because these routines do most of the work
> > already and just avoid the last few steps). Which way is preferred?
>
> I think changing xaccPrintAmount() the way you described here is a good
> idea
> in any case and should be implemented once you have that routine.
>

A patch for printing is shown below. However, since the new
convert_to_decimal function may be useful outside of printing, should I move
it into gnc-numeric.c? And if so, what name would you like? Do you like the
style of the parameters? The idea is that you pass in a pointer to your
gnc_numeric, and convert_to_decimal() returns TRUE if your gnc_numeric has
been converted. Otherwise FALSE is returned and your gnc_numeric is
unchanged.

> Finally, I have already written such a routine. I hope that I have not
> > reinvented the wheel...
>
> Good to hear! Thanks for your work!
>
> Christian
>

-Charles


Index: gnc-ui-util.c
===================================================================
--- gnc-ui-util.c       (revision 17402)
+++ gnc-ui-util.c       (working copy)
@@ -1045,6 +1045,68 @@
 }

 static gboolean
+convert_to_decimal(gnc_numeric *val, guint8 *max_decimal_places_p)
+{
+  guint8 max_decimal_places = 0;
+  gnc_numeric converted_val;
+  gint64 fraction;
+
+  g_return_val_if_fail(val, FALSE);
+
+  converted_val = *val;
+  fraction = converted_val.denom;
+  if (fraction <= 0)
+    return FALSE;
+
+  while (fraction != 1)
+  {
+    switch (fraction % 10)
+    {
+      case 0:
+        fraction = fraction / 10;
+        break;
+
+      case 5:
+        converted_val = gnc_numeric_mul(converted_val,
+                                        gnc_numeric_create(2, 2),
+                                        GNC_DENOM_AUTO,
+                                        GNC_HOW_DENOM_EXACT |
+                                        GNC_HOW_RND_NEVER);
+        if (gnc_numeric_check(converted_val) != GNC_ERROR_OK)
+          return FALSE;
+        fraction = fraction / 5;
+        break;
+
+      case 2:
+      case 4:
+      case 6:
+      case 8:
+        converted_val = gnc_numeric_mul(converted_val,
+                                        gnc_numeric_create(5, 5),
+                                        GNC_DENOM_AUTO,
+                                        GNC_HOW_DENOM_EXACT |
+                                        GNC_HOW_RND_NEVER);
+        if (gnc_numeric_check(converted_val) != GNC_ERROR_OK)
+          return FALSE;
+        fraction = fraction / 2;
+        break;
+
+      default:
+        return FALSE;
+    }
+
+    max_decimal_places += 1;
+  }
+
+  if (max_decimal_places_p)
+    *max_decimal_places_p = max_decimal_places;
+
+  *val = converted_val;
+
+  return TRUE;
+}
+
+static gboolean
 is_decimal_fraction (int fraction, guint8 *max_decimal_places_p)
 {
   guint8 max_decimal_places = 0;
@@ -1383,7 +1445,7 @@
   /* at this point, buf contains the whole part of the number */

   /* If it's not decimal, print the fraction as an expression */
-  if (!is_decimal_fraction (val.denom, NULL))
+  if (!convert_to_decimal(&val, NULL))
   {
     if (!gnc_numeric_zero_p (val))
     {


More information about the gnucash-devel mailing list