Skip to content

Commit 786c3c0

Browse files
committed
Fix imprecision from interval rounding of multiplication/division.
Bruce, Michael Glaesemann
1 parent 548e2c0 commit 786c3c0

File tree

2 files changed

+40
-26
lines changed

2 files changed

+40
-26
lines changed

src/backend/utils/adt/timestamp.c

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.166 2006/09/03 03:34:04 momjian Exp $
11+
* $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.167 2006/09/05 01:13:39 momjian Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -2514,28 +2514,34 @@ interval_mul(PG_FUNCTION_ARGS)
25142514
/*
25152515
* Fractional months full days into days.
25162516
*
2517-
* The remainders suffer from float rounding, so instead of
2518-
* doing the computation using just the remainder, we calculate
2519-
* the total number of days and subtract. Specifically, we are
2520-
* multipling by DAYS_PER_MONTH before dividing by factor.
2521-
* This greatly reduces rounding errors.
2517+
* Floating point calculation are inherently inprecise, so these
2518+
* calculations are crafted to produce the most reliable result
2519+
* possible. TSROUND() is needed to more accurately produce whole
2520+
* numbers where appropriate.
25222521
*/
2523-
month_remainder_days = (orig_month * (double)DAYS_PER_MONTH) * factor -
2524-
result->month * (double)DAYS_PER_MONTH;
2525-
sec_remainder = (orig_day * (double)SECS_PER_DAY) * factor -
2526-
result->day * (double)SECS_PER_DAY +
2527-
(month_remainder_days - (int32) month_remainder_days) * SECS_PER_DAY;
2522+
month_remainder_days = (orig_month * factor - result->month) * DAYS_PER_MONTH;
2523+
month_remainder_days = TSROUND(month_remainder_days);
2524+
sec_remainder = (orig_day * factor - result->day +
2525+
month_remainder_days - (int)month_remainder_days) * SECS_PER_DAY;
2526+
sec_remainder = TSROUND(sec_remainder);
2527+
2528+
/*
2529+
* Might have 24:00:00 hours due to rounding, or >24 hours because of
2530+
* time cascade from months and days. It might still be >24 if the
2531+
* combination of cascade and the seconds factor operation itself.
2532+
*/
2533+
if (Abs(sec_remainder) >= SECS_PER_DAY)
2534+
{
2535+
result->day += (int)(sec_remainder / SECS_PER_DAY);
2536+
sec_remainder -= (int)(sec_remainder / SECS_PER_DAY) * SECS_PER_DAY;
2537+
}
25282538

25292539
/* cascade units down */
25302540
result->day += (int32) month_remainder_days;
25312541
#ifdef HAVE_INT64_TIMESTAMP
25322542
result->time = rint(span->time * factor + sec_remainder * USECS_PER_SEC);
25332543
#else
2534-
/*
2535-
* TSROUND() needed to prevent -146:23:60.00 output on PowerPC for
2536-
* SELECT interval '-41 mon -12 days -360:00' * 0.3;
2537-
*/
2538-
result->time = span->time * factor + TSROUND(sec_remainder);
2544+
result->time = span->time * factor + sec_remainder;
25392545
#endif
25402546

25412547
PG_RETURN_INTERVAL_P(result);
@@ -2574,19 +2580,24 @@ interval_div(PG_FUNCTION_ARGS)
25742580
* Fractional months full days into days. See comment in
25752581
* interval_mul().
25762582
*/
2577-
month_remainder_days = (orig_month * (double)DAYS_PER_MONTH) / factor -
2578-
result->month * (double)DAYS_PER_MONTH;
2579-
sec_remainder = (orig_day * (double)SECS_PER_DAY) / factor -
2580-
result->day * (double)SECS_PER_DAY +
2581-
(month_remainder_days - (int32) month_remainder_days) * SECS_PER_DAY;
2583+
month_remainder_days = (orig_month / factor - result->month) * DAYS_PER_MONTH;
2584+
month_remainder_days = TSROUND(month_remainder_days);
2585+
sec_remainder = (orig_day / factor - result->day +
2586+
month_remainder_days - (int)month_remainder_days) * SECS_PER_DAY;
2587+
sec_remainder = TSROUND(sec_remainder);
2588+
if (Abs(sec_remainder) >= SECS_PER_DAY)
2589+
{
2590+
result->day += (int)(sec_remainder / SECS_PER_DAY);
2591+
sec_remainder -= (int)(sec_remainder / SECS_PER_DAY) * SECS_PER_DAY;
2592+
}
25822593

25832594
/* cascade units down */
25842595
result->day += (int32) month_remainder_days;
25852596
#ifdef HAVE_INT64_TIMESTAMP
25862597
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
25872598
#else
25882599
/* See TSROUND comment in interval_mul(). */
2589-
result->time = span->time / factor + TSROUND(sec_remainder);
2600+
result->time = span->time / factor + sec_remainder;
25902601
#endif
25912602

25922603
PG_RETURN_INTERVAL_P(result);

src/include/utils/timestamp.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
77
* Portions Copyright (c) 1994, Regents of the University of California
88
*
9-
* $PostgreSQL: pgsql/src/include/utils/timestamp.h,v 1.62 2006/07/13 16:49:20 momjian Exp $
9+
* $PostgreSQL: pgsql/src/include/utils/timestamp.h,v 1.63 2006/09/05 01:13:40 momjian Exp $
1010
*
1111
*-------------------------------------------------------------------------
1212
*/
@@ -161,11 +161,14 @@ typedef int32 fsec_t;
161161

162162
typedef double fsec_t;
163163

164-
/* round off to MAX_TIMESTAMP_PRECISION decimal places */
165-
/* note: this is also used for rounding off intervals */
164+
#endif
165+
166+
/*
167+
* Round off to MAX_TIMESTAMP_PRECISION decimal places.
168+
* Note: this is also used for rounding off intervals.
169+
*/
166170
#define TS_PREC_INV 1000000.0
167171
#define TSROUND(j) (rint(((double) (j)) * TS_PREC_INV) / TS_PREC_INV)
168-
#endif
169172

170173
#define TIMESTAMP_MASK(b) (1 << (b))
171174
#define INTERVAL_MASK(b) (1 << (b))

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy