Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit786c3c0

Browse files
committed
Fix imprecision from interval rounding of multiplication/division.
Bruce, Michael Glaesemann
1 parent548e2c0 commit786c3c0

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
#ifdefHAVE_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
#ifdefHAVE_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
typedefdoublefsec_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
#defineTS_PREC_INV 1000000.0
167171
#defineTSROUND(j) (rint(((double) (j)) * TS_PREC_INV) / TS_PREC_INV)
168-
#endif
169172

170173
#defineTIMESTAMP_MASK(b) (1 << (b))
171174
#defineINTERVAL_MASK(b) (1 << (b))

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp