Fix imprecision from interval rounding of multiplication/division.
Bruce, Michael Glaesemann
This commit is contained in:
parent
548e2c0a01
commit
786c3c0355
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.166 2006/09/03 03:34:04 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.167 2006/09/05 01:13:39 momjian Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -2514,28 +2514,34 @@ interval_mul(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Fractional months full days into days.
|
||||
*
|
||||
* The remainders suffer from float rounding, so instead of
|
||||
* doing the computation using just the remainder, we calculate
|
||||
* the total number of days and subtract. Specifically, we are
|
||||
* multipling by DAYS_PER_MONTH before dividing by factor.
|
||||
* This greatly reduces rounding errors.
|
||||
* Floating point calculation are inherently inprecise, so these
|
||||
* calculations are crafted to produce the most reliable result
|
||||
* possible. TSROUND() is needed to more accurately produce whole
|
||||
* numbers where appropriate.
|
||||
*/
|
||||
month_remainder_days = (orig_month * (double)DAYS_PER_MONTH) * factor -
|
||||
result->month * (double)DAYS_PER_MONTH;
|
||||
sec_remainder = (orig_day * (double)SECS_PER_DAY) * factor -
|
||||
result->day * (double)SECS_PER_DAY +
|
||||
(month_remainder_days - (int32) month_remainder_days) * SECS_PER_DAY;
|
||||
month_remainder_days = (orig_month * factor - result->month) * DAYS_PER_MONTH;
|
||||
month_remainder_days = TSROUND(month_remainder_days);
|
||||
sec_remainder = (orig_day * factor - result->day +
|
||||
month_remainder_days - (int)month_remainder_days) * SECS_PER_DAY;
|
||||
sec_remainder = TSROUND(sec_remainder);
|
||||
|
||||
/*
|
||||
* Might have 24:00:00 hours due to rounding, or >24 hours because of
|
||||
* time cascade from months and days. It might still be >24 if the
|
||||
* combination of cascade and the seconds factor operation itself.
|
||||
*/
|
||||
if (Abs(sec_remainder) >= SECS_PER_DAY)
|
||||
{
|
||||
result->day += (int)(sec_remainder / SECS_PER_DAY);
|
||||
sec_remainder -= (int)(sec_remainder / SECS_PER_DAY) * SECS_PER_DAY;
|
||||
}
|
||||
|
||||
/* cascade units down */
|
||||
result->day += (int32) month_remainder_days;
|
||||
#ifdef HAVE_INT64_TIMESTAMP
|
||||
result->time = rint(span->time * factor + sec_remainder * USECS_PER_SEC);
|
||||
#else
|
||||
/*
|
||||
* TSROUND() needed to prevent -146:23:60.00 output on PowerPC for
|
||||
* SELECT interval '-41 mon -12 days -360:00' * 0.3;
|
||||
*/
|
||||
result->time = span->time * factor + TSROUND(sec_remainder);
|
||||
result->time = span->time * factor + sec_remainder;
|
||||
#endif
|
||||
|
||||
PG_RETURN_INTERVAL_P(result);
|
||||
@ -2574,11 +2580,16 @@ interval_div(PG_FUNCTION_ARGS)
|
||||
* Fractional months full days into days. See comment in
|
||||
* interval_mul().
|
||||
*/
|
||||
month_remainder_days = (orig_month * (double)DAYS_PER_MONTH) / factor -
|
||||
result->month * (double)DAYS_PER_MONTH;
|
||||
sec_remainder = (orig_day * (double)SECS_PER_DAY) / factor -
|
||||
result->day * (double)SECS_PER_DAY +
|
||||
(month_remainder_days - (int32) month_remainder_days) * SECS_PER_DAY;
|
||||
month_remainder_days = (orig_month / factor - result->month) * DAYS_PER_MONTH;
|
||||
month_remainder_days = TSROUND(month_remainder_days);
|
||||
sec_remainder = (orig_day / factor - result->day +
|
||||
month_remainder_days - (int)month_remainder_days) * SECS_PER_DAY;
|
||||
sec_remainder = TSROUND(sec_remainder);
|
||||
if (Abs(sec_remainder) >= SECS_PER_DAY)
|
||||
{
|
||||
result->day += (int)(sec_remainder / SECS_PER_DAY);
|
||||
sec_remainder -= (int)(sec_remainder / SECS_PER_DAY) * SECS_PER_DAY;
|
||||
}
|
||||
|
||||
/* cascade units down */
|
||||
result->day += (int32) month_remainder_days;
|
||||
@ -2586,7 +2597,7 @@ interval_div(PG_FUNCTION_ARGS)
|
||||
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
|
||||
#else
|
||||
/* See TSROUND comment in interval_mul(). */
|
||||
result->time = span->time / factor + TSROUND(sec_remainder);
|
||||
result->time = span->time / factor + sec_remainder;
|
||||
#endif
|
||||
|
||||
PG_RETURN_INTERVAL_P(result);
|
||||
|
@ -6,7 +6,7 @@
|
||||
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/utils/timestamp.h,v 1.62 2006/07/13 16:49:20 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/include/utils/timestamp.h,v 1.63 2006/09/05 01:13:40 momjian Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -161,11 +161,14 @@ typedef int32 fsec_t;
|
||||
|
||||
typedef double fsec_t;
|
||||
|
||||
/* round off to MAX_TIMESTAMP_PRECISION decimal places */
|
||||
/* note: this is also used for rounding off intervals */
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 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))
|
||||
|
Loading…
Reference in New Issue
Block a user