Repair inconsistent rounding behavior for timestamp, time, interval,
authorTom Lane
Thu, 9 Jan 2003 01:06:57 +0000 (01:06 +0000)
committerTom Lane
Thu, 9 Jan 2003 01:06:57 +0000 (01:06 +0000)
per gripe from Csaba Nagy.  There is still potential for platform-specific
behavior for values that are exactly halfway between integers, but at
least we now get the expected answer for all other cases.

src/backend/utils/adt/date.c
src/backend/utils/adt/timestamp.c

index 3b921258890d14fdef817f2d306e18be2162d661..b952480f2fe303991b397203fcc2e89be6b145a3 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/utils/adt/date.c,v 1.74 2002/11/21 23:31:20 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/utils/adt/date.c,v 1.75 2003/01/09 01:06:57 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -630,12 +630,12 @@ AdjustTimeForTypmod(TimeADT *time, int32 typmod)
    };
 
    static const int64 TimeOffsets[MAX_TIMESTAMP_PRECISION + 1] = {
-       INT64CONST(-500000),
-       INT64CONST(-50000),
-       INT64CONST(-5000),
-       INT64CONST(-500),
-       INT64CONST(-50),
-       INT64CONST(-5),
+       INT64CONST(500000),
+       INT64CONST(50000),
+       INT64CONST(5000),
+       INT64CONST(500),
+       INT64CONST(50),
+       INT64CONST(5),
        INT64CONST(0)
    };
 
@@ -649,52 +649,33 @@ AdjustTimeForTypmod(TimeADT *time, int32 typmod)
        100000,
        1000000
    };
-
-   static const double TimeOffsets[MAX_TIMESTAMP_PRECISION + 1] = {
-       0.5,
-       0.05,
-       0.005,
-       0.0005,
-       0.00005,
-       0.000005,
-       0.0000005
-   };
 #endif
 
    if ((typmod >= 0) && (typmod <= MAX_TIME_PRECISION))
    {
+       /*
+        * Note: this round-to-nearest code is not completely consistent
+        * about rounding values that are exactly halfway between integral
+        * values.  On most platforms, rint() will implement round-to-nearest,
+        * but the integer code always rounds up (away from zero).  Is it
+        * worth trying to be consistent?
+        */
 #ifdef HAVE_INT64_TIMESTAMP
-       /* we have different truncation behavior depending on sign */
        if (*time >= INT64CONST(0))
-       {
-           *time = ((*time / TimeScales[typmod])
-                    * TimeScales[typmod]);
-       }
-       else
        {
            *time = (((*time + TimeOffsets[typmod]) / TimeScales[typmod])
                     * TimeScales[typmod]);
        }
-#else
-       /* we have different truncation behavior depending on sign */
-       if (*time >= 0)
-       {
-           *time = (rint(((double) *time) * TimeScales[typmod])
-                    / TimeScales[typmod]);
-       }
        else
        {
-           /*
-            * Scale and truncate first, then add to help the rounding
-            * behavior
-            */
-           *time = (rint((((double) *time) * TimeScales[typmod]) + TimeOffsets[typmod])
-                    / TimeScales[typmod]);
+           *time = - ((((- *time) + TimeOffsets[typmod]) / TimeScales[typmod])
+                      * TimeScales[typmod]);
        }
+#else
+       *time = (rint(((double) *time) * TimeScales[typmod])
+                / TimeScales[typmod]);
 #endif
    }
-
-   return;
 }
 
 
index 885d3992fd44ab5304e7845ab23a89e9507b1a14..19fb9c118438135748919e75ba281b9f7d644ab8 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/utils/adt/timestamp.c,v 1.75 2002/11/12 00:39:08 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/utils/adt/timestamp.c,v 1.76 2003/01/09 01:06:57 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -175,12 +175,12 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
    };
 
    static const int64 TimestampOffsets[MAX_TIMESTAMP_PRECISION + 1] = {
-       INT64CONST(-500000),
-       INT64CONST(-50000),
-       INT64CONST(-5000),
-       INT64CONST(-500),
-       INT64CONST(-50),
-       INT64CONST(-5),
+       INT64CONST(500000),
+       INT64CONST(50000),
+       INT64CONST(5000),
+       INT64CONST(500),
+       INT64CONST(50),
+       INT64CONST(5),
        INT64CONST(0)
    };
 
@@ -194,16 +194,6 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
        100000,
        1000000
    };
-
-   static const double TimestampOffsets[MAX_TIMESTAMP_PRECISION + 1] = {
-       0.5,
-       0.05,
-       0.005,
-       0.0005,
-       0.00005,
-       0.000005,
-       0.0000005
-   };
 #endif
 
    if (!TIMESTAMP_NOT_FINITE(*time)
@@ -213,34 +203,27 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
            elog(ERROR, "TIMESTAMP(%d) precision must be between %d and %d",
                 typmod, 0, MAX_TIMESTAMP_PRECISION);
 
+       /*
+        * Note: this round-to-nearest code is not completely consistent
+        * about rounding values that are exactly halfway between integral
+        * values.  On most platforms, rint() will implement round-to-nearest,
+        * but the integer code always rounds up (away from zero).  Is it
+        * worth trying to be consistent?
+        */
 #ifdef HAVE_INT64_TIMESTAMP
-       /* we have different truncation behavior depending on sign */
        if (*time >= INT64CONST(0))
-       {
-           *time = ((*time / TimestampScales[typmod])
-                    * TimestampScales[typmod]);
-       }
-       else
        {
            *time = (((*time + TimestampOffsets[typmod]) / TimestampScales[typmod])
                     * TimestampScales[typmod]);
        }
-#else
-       /* we have different truncation behavior depending on sign */
-       if (*time >= 0)
-       {
-           *time = (rint(((double) *time) * TimestampScales[typmod])
-                    / TimestampScales[typmod]);
-       }
        else
        {
-           /*
-            * Scale and truncate first, then add to help the rounding
-            * behavior
-            */
-           *time = (rint((((double) *time) * TimestampScales[typmod]) + TimestampOffsets[typmod])
-                    / TimestampScales[typmod]);
+           *time = - ((((- *time) + TimestampOffsets[typmod]) / TimestampScales[typmod])
+                      * TimestampScales[typmod]);
        }
+#else
+       *time = (rint(((double) *time) * TimestampScales[typmod])
+                / TimestampScales[typmod]);
 #endif
    }
 }
@@ -474,12 +457,12 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod)
    };
 
    static const int64 IntervalOffsets[MAX_INTERVAL_PRECISION + 1] = {
-       INT64CONST(-500000),
-       INT64CONST(-50000),
-       INT64CONST(-5000),
-       INT64CONST(-500),
-       INT64CONST(-50),
-       INT64CONST(-5),
+       INT64CONST(500000),
+       INT64CONST(50000),
+       INT64CONST(5000),
+       INT64CONST(500),
+       INT64CONST(50),
+       INT64CONST(5),
        INT64CONST(0)
    };
 
@@ -493,16 +476,6 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod)
        100000,
        1000000
    };
-
-   static const double IntervalOffsets[MAX_INTERVAL_PRECISION + 1] = {
-       0.5,
-       0.05,
-       0.005,
-       0.0005,
-       0.00005,
-       0.000005,
-       0.0000005
-   };
 #endif
 
    /*
@@ -701,30 +674,27 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod)
                elog(ERROR, "INTERVAL(%d) precision must be between %d and %d",
                     precision, 0, MAX_INTERVAL_PRECISION);
 
+           /*
+            * Note: this round-to-nearest code is not completely consistent
+            * about rounding values that are exactly halfway between integral
+            * values.  On most platforms, rint() will implement round-to-nearest,
+            * but the integer code always rounds up (away from zero).  Is it
+            * worth trying to be consistent?
+            */
 #ifdef HAVE_INT64_TIMESTAMP
-           /* we have different truncation behavior depending on sign */
            if (interval->time >= INT64CONST(0))
-           {
-               interval->time = ((interval->time / IntervalScales[precision])
-                                 * IntervalScales[precision]);
-           }
-           else
            {
                interval->time = (((interval->time + IntervalOffsets[precision]) / IntervalScales[precision])
                                  * IntervalScales[precision]);
            }
-#else
-           /* we have different truncation behavior depending on sign */
-           if (interval->time >= 0)
-           {
-               interval->time = (rint(((double) interval->time) * IntervalScales[precision])
-                                 / IntervalScales[precision]);
-           }
            else
            {
-               interval->time = (rint((((double) interval->time) + IntervalOffsets[precision])
-               * IntervalScales[precision]) / IntervalScales[precision]);
+               interval->time = - (((-interval->time + IntervalOffsets[precision]) / IntervalScales[precision])
+                                   * IntervalScales[precision]);
            }
+#else
+           interval->time = (rint(((double) interval->time) * IntervalScales[precision])
+                             / IntervalScales[precision]);
 #endif
        }
    }