Re: [HACKERS] Interval aggregate regression failure - Mailing list pgsql-patches

From Bruce Momjian
Subject Re: [HACKERS] Interval aggregate regression failure
Date
Msg-id 200608300350.k7U3oeH06639@momjian.us
Whole thread Raw
In response to Re: [HACKERS] Interval aggregate regression failure (expected seems  (Michael Glaesemann <grzm@seespotcode.net>)
Responses Re: [HACKERS] Interval aggregate regression failure (expected seems
Re: [HACKERS] Interval aggregate regression failure (expected seems
List pgsql-patches
Michael Glaesemann wrote:
> On Aug 30, 2006, at 7:12 , Bruce Momjian wrote:
>
> > Here are the results using my newest patch:
> >
> >     test=> select interval '41 mon 12 days 360:00' / 10 as quotient_a
> >          , interval '41 mon -12 days -360:00' / 10 as quotient_b
> >          , interval '-41 mon 12 days 360:00' / 10 as quotient_c
> >          , interval '-41 mon -12 days -360:00' / 10 as quotient_d;
> >            quotient_a       |       quotient_b        |
> > quotient_c         |        quotient_d
> >     ------------------------+-------------------------
> > +---------------------------+---------------------------
> >      4 mons 4 days 40:48:00 | 4 mons 2 days -40:48:00 | -4 mons -2
> > days +40:48:00 | -4 mons -4 days -40:48:00
> >     (1 row)
> >
> >     test=> select interval '41 mon 12 days 360:00' * 0.3 as product_a
> >          , interval '41 mon -12 days -360:00' * 0.3 as product_b
> >          , interval '-41 mon 12 days 360:00' * 0.3 as product_c
> >          , interval '-41 mon -12 days -360:00' * 0.3 as product_d;
> >             product_a         |        product_b         |
> > product_c          |          product_d
> >     --------------------------+--------------------------
> > +-----------------------------+------------------------------
> >      1 year 12 days 122:24:00 | 1 year 6 days -122:24:00 | -1 years -6
> > days +122:24:00 | -1 years -12 days -122:24:00
> >     (1 row)
> >
> > I see no "23:60" entries.
>
> Using Bruce's newest patch, I still get the "23:60" entries on my
> machine (no integer-datetimes)


Strange, I do not see that here.  Is there something wrong with our
hour/minute display?  Someone posted a patch a few days ago for that.

Here is a test program.  What does it show for you?

    #include <stdio.h>


    int
    main(int argc, char *argv[])
    {
        double x;

        x = 41;
        x = x / 10.0;
        printf("%f\n", x);
        x = x - (int)x;
        x = x * 30;
        printf("%15.15f\n", x);
        x = 0.1 * 30;
        printf("%15.15f\n", x);
        return 0;
    }

The output for me is:

    4.100000000000000
    2.999999999999989
    3.000000000000000


>
> select version();
>
> version
> ------------------------------------------------------------------------
> ------------------------------------------------------------------------
> -
> PostgreSQL 8.2devel on powerpc-apple-darwin8.7.0, compiled by GCC
> powerpc-apple-darwin8-gcc-4.0.1 (GCC) 4.0.1 (Apple Computer, Inc.
> build 5341)
> (1 row)

Powerpc.  Hmmm.  I am on Intel.

> select interval '41 mon 12 days 360:00' / 10 as quotient_a
>      , interval '41 mon -12 days -360:00' / 10 as quotient_b
>      , interval '-41 mon 12 days 360:00' / 10 as quotient_c
>      , interval '-41 mon -12 days -360:00' / 10 as quotient_d;
>         quotient_a       |       quotient_b        |
> quotient_c         |        quotient_d
> ------------------------+-------------------------
> +---------------------------+---------------------------
> 4 mons 4 days 40:48:00 | 4 mons 2 days -40:48:00 | -4 mons -2 days
> +40:48:00 | -4 mons -4 days -40:48:00
> (1 row)
>
> select interval '41 mon 12 days 360:00' * 0.3 as product_a
>      , interval '41 mon -12 days -360:00' * 0.3 as product_b
>      , interval '-41 mon 12 days 360:00' * 0.3 as product_c
>      , interval '-41 mon -12 days -360:00' * 0.3 as product_d;
>          product_a         |          product_b          |
> product_c          |            product_d
> --------------------------+-----------------------------
> +-----------------------------+---------------------------------
> 1 year 12 days 122:24:00 | 1 year 6 days -122:23:60.00 | -1 years -6
> days +122:24:00 | -1 years -12 days -122:23:60.00
> (1 row)
>

Yea, I see that -122:23:60.00.

> > The code assume if it is within 0.000001 of a whole number, it
> > should be
> > rounded to a whole number. Patch attached with comments added.
>
> >       /* fractional months full days into days */
> >       month_remainder_days = month_remainder * DAYS_PER_MONTH;
> > +     /*
> > +      *    The remainders suffer from float rounding, so if they are
> > +      *    within 0.000001 of an integer, we round them to integers.
> > +      */
> > +     if (month_remainder_days != (int32)month_remainder_days &&
> > +         TSROUND(month_remainder_days) == rint(month_remainder_days))
> > +         month_remainder_days = rint(month_remainder_days);
> >       result->day += (int32) month_remainder_days;
> >
>
> Don't we want to be checking for rounding at the usec level rather
> than 0.000001 of a day? I think this should be
>
>     if (month_remainder_days != (int32)month_remainder_days &&
>         TSROUND(month_remainder_days * SECS_PER_DAY) ==
>         rint(month_remainder_days * SECS_PER_DAY))
>         month_remainder_days = rint(month_remainder_days);
>

Good idea.  I updated the patch to do that.

> Another question I have concerns the month_remainder_days != (int32)
> month_remainder_days comparison. If I understand it correctly, if the
> TSROUND == rint portion is true, the first part is true. Or is this
> just a quick, fast check to see if it's necessary to do a more
> computationally intensive check?

Yea, just an optimization, but I was worried that the computations might
throw problems for certain numbers, so I figured I would only trigger it
when necessary.

> TSROUND isn't defined for HAVE_INT64_TIMESTAMP. My first attempt at
> performing a corresponding comparison doesn't work:
>
> +     if (month_remainder_days != (int32) month_remainder_days &&
> + #ifdef HAVE_INT64_TIMESTAMP
> +         rint(month_remainder_days * USECS_PER_DAY) ==
> +            (month_remainder_days * USECS_PER_DAY))
> + #else
> +         TSROUND(month_remainder_days * SECS_PER_DAY) ==
> +            rint(month_remainder_days * SECS_PER_DAY))
> + #endif
> +         month_remainder_days = rint(month_remainder_days);
>
> Anyway, I'll pound on this some more tonight.

You don't want to do any conditional tests.  SECS_PER_DAY is all you
want.  Those two macros are useful only in representing the internal
storage format, not for float compuations of rounding.

Patch attached.  It also fixes a regression test output too.

--
  Bruce Momjian   bruce@momjian.us
  EnterpriseDB    http://www.enterprisedb.com

  + If your life is a hard drive, Christ can be your backup. +
Index: src/backend/utils/adt/timestamp.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/adt/timestamp.c,v
retrieving revision 1.165
diff -c -c -r1.165 timestamp.c
*** src/backend/utils/adt/timestamp.c    13 Jul 2006 16:49:16 -0000    1.165
--- src/backend/utils/adt/timestamp.c    30 Aug 2006 03:45:41 -0000
***************
*** 2518,2523 ****
--- 2518,2532 ----

      /* fractional months full days into days */
      month_remainder_days = month_remainder * DAYS_PER_MONTH;
+     /*
+      *    The remainders suffer from float rounding, so if they are
+      *    within 1us of an integer, we round them to integers.
+      *    It seems doing two multiplies is causing the imprecision.
+      */
+     if (month_remainder_days != (int32)month_remainder_days &&
+         TSROUND(month_remainder_days * SECS_PER_DAY) ==
+         rint(month_remainder_days * SECS_PER_DAY))
+         month_remainder_days = rint(month_remainder_days);
      result->day += (int32) month_remainder_days;
      /* fractional months partial days into time */
      day_remainder += month_remainder_days - (int32) month_remainder_days;
***************
*** 2571,2576 ****
--- 2580,2595 ----

      /* fractional months full days into days */
      month_remainder_days = month_remainder * DAYS_PER_MONTH;
+     /*
+      *    The remainders suffer from float rounding, so if they are
+      *    within 1us of an integer, we round them to integers.
+      *    It seems doing a division and multiplies is causing the
+      *    imprecision.
+      */
+     if (month_remainder_days != (int32)month_remainder_days &&
+         TSROUND(month_remainder_days * SECS_PER_DAY) ==
+         rint(month_remainder_days * SECS_PER_DAY))
+         month_remainder_days = rint(month_remainder_days);
      result->day += (int32) month_remainder_days;
      /* fractional months partial days into time */
      day_remainder += month_remainder_days - (int32) month_remainder_days;
Index: src/include/utils/timestamp.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/utils/timestamp.h,v
retrieving revision 1.62
diff -c -c -r1.62 timestamp.h
*** src/include/utils/timestamp.h    13 Jul 2006 16:49:20 -0000    1.62
--- src/include/utils/timestamp.h    30 Aug 2006 03:45:42 -0000
***************
*** 161,171 ****

  typedef double fsec_t;

! /* round off to MAX_TIMESTAMP_PRECISION decimal places */
! /* note: this is also used for rounding off intervals */
  #define TS_PREC_INV 1000000.0
  #define TSROUND(j) (rint(((double) (j)) * TS_PREC_INV) / TS_PREC_INV)
- #endif

  #define TIMESTAMP_MASK(b) (1 << (b))
  #define INTERVAL_MASK(b) (1 << (b))
--- 161,175 ----

  typedef double fsec_t;

! #endif
!
! /*
!  *    Round off to MAX_TIMESTAMP_PRECISION decimal places.
!  *    This is also used for rounding off intervals, and
!  *    for rounding interval multiplication/division calculations.
!  */
  #define TS_PREC_INV 1000000.0
  #define TSROUND(j) (rint(((double) (j)) * TS_PREC_INV) / TS_PREC_INV)

  #define TIMESTAMP_MASK(b) (1 << (b))
  #define INTERVAL_MASK(b) (1 << (b))
Index: src/test/regress/expected/interval.out
===================================================================
RCS file: /cvsroot/pgsql/src/test/regress/expected/interval.out,v
retrieving revision 1.15
diff -c -c -r1.15 interval.out
*** src/test/regress/expected/interval.out    6 Mar 2006 22:49:17 -0000    1.15
--- src/test/regress/expected/interval.out    30 Aug 2006 03:45:43 -0000
***************
*** 218,224 ****
  select avg(f1) from interval_tbl;
                         avg
  -------------------------------------------------
!  @ 4 years 1 mon 9 days 28 hours 18 mins 23 secs
  (1 row)

  -- test long interval input
--- 218,224 ----
  select avg(f1) from interval_tbl;
                         avg
  -------------------------------------------------
!  @ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
  (1 row)

  -- test long interval input

pgsql-patches by date:

Previous
From: Michael Glaesemann
Date:
Subject: Re: [HACKERS] Interval aggregate regression failure (expected seems
Next
From: Bruce Momjian
Date:
Subject: Re: Interval month, week -> day