8
8
*
9
9
*
10
10
* IDENTIFICATION
11
- * $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.165 2006/07/13 16:49:16 momjian Exp $
11
+ * $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.166 2006/09/03 03:34:04 momjian Exp $
12
12
*
13
13
*-------------------------------------------------------------------------
14
14
*/
@@ -2492,19 +2492,14 @@ interval_mul(PG_FUNCTION_ARGS)
2492
2492
{
2493
2493
Interval * span = PG_GETARG_INTERVAL_P (0 );
2494
2494
float8 factor = PG_GETARG_FLOAT8 (1 );
2495
- double month_remainder ,
2496
- day_remainder ,
2497
- month_remainder_days ;
2495
+ double month_remainder_days ,sec_remainder ;
2496
+ int32 orig_month = span -> month ,orig_day = span -> day ;
2498
2497
Interval * result ;
2499
2498
2500
2499
result = (Interval * )palloc (sizeof (Interval ));
2501
2500
2502
- month_remainder = span -> month * factor ;
2503
- day_remainder = span -> day * factor ;
2504
- result -> month = (int32 )month_remainder ;
2505
- result -> day = (int32 )day_remainder ;
2506
- month_remainder -= result -> month ;
2507
- day_remainder -= result -> day ;
2501
+ result -> month = (int32 ) (span -> month * factor );
2502
+ result -> day = (int32 ) (span -> day * factor );
2508
2503
2509
2504
/*
2510
2505
* The above correctly handles the whole-number part of the month and day
@@ -2516,16 +2511,31 @@ interval_mul(PG_FUNCTION_ARGS)
2516
2511
* using justify_hours and/or justify_days.
2517
2512
*/
2518
2513
2519
- /* fractional months full days into days */
2520
- month_remainder_days = month_remainder * DAYS_PER_MONTH ;
2521
- result -> day += (int32 )month_remainder_days ;
2522
- /* fractional months partial days into time */
2523
- day_remainder += month_remainder_days - (int32 )month_remainder_days ;
2514
+ /*
2515
+ *Fractional months full days into days.
2516
+ *
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.
2522
+ */
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 ;
2524
2528
2529
+ /* cascade units down */
2530
+ result -> day += (int32 )month_remainder_days ;
2525
2531
#ifdef HAVE_INT64_TIMESTAMP
2526
- result -> time = rint (span -> time * factor + day_remainder * USECS_PER_DAY );
2532
+ result -> time = rint (span -> time * factor + sec_remainder * USECS_PER_SEC );
2527
2533
#else
2528
- result -> time = span -> time * factor + day_remainder * SECS_PER_DAY ;
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 );
2529
2539
#endif
2530
2540
2531
2541
PG_RETURN_INTERVAL_P (result );
@@ -2546,39 +2556,37 @@ interval_div(PG_FUNCTION_ARGS)
2546
2556
{
2547
2557
Interval * span = PG_GETARG_INTERVAL_P (0 );
2548
2558
float8 factor = PG_GETARG_FLOAT8 (1 );
2549
- double month_remainder ,
2550
- day_remainder ,
2551
- month_remainder_days ;
2559
+ double month_remainder_days ,sec_remainder ;
2560
+ int32 orig_month = span -> month ,orig_day = span -> day ;
2552
2561
Interval * result ;
2553
-
2562
+
2554
2563
result = (Interval * )palloc (sizeof (Interval ));
2555
2564
2556
2565
if (factor == 0.0 )
2557
2566
ereport (ERROR ,
2558
2567
(errcode (ERRCODE_DIVISION_BY_ZERO ),
2559
2568
errmsg ("division by zero" )));
2560
2569
2561
- month_remainder = span -> month /factor ;
2562
- day_remainder = span -> day /factor ;
2563
- result -> month = (int32 )month_remainder ;
2564
- result -> day = (int32 )day_remainder ;
2565
- month_remainder -= result -> month ;
2566
- day_remainder -= result -> day ;
2570
+ result -> month = (int32 ) (span -> month /factor );
2571
+ result -> day = (int32 ) (span -> day /factor );
2567
2572
2568
2573
/*
2569
- * Handle any fractional parts the same way as in interval_mul.
2574
+ *Fractional months full days into days. See comment in
2575
+ *interval_mul().
2570
2576
*/
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 ;
2571
2582
2572
- /* fractional months full days into days */
2573
- month_remainder_days = month_remainder * DAYS_PER_MONTH ;
2583
+ /* cascade units down */
2574
2584
result -> day += (int32 )month_remainder_days ;
2575
- /* fractional months partial days into time */
2576
- day_remainder += month_remainder_days - (int32 )month_remainder_days ;
2577
-
2578
2585
#ifdef HAVE_INT64_TIMESTAMP
2579
- result -> time = rint (span -> time /factor + day_remainder * USECS_PER_DAY );
2586
+ result -> time = rint (span -> time /factor + sec_remainder * USECS_PER_SEC );
2580
2587
#else
2581
- result -> time = span -> time /factor + day_remainder * SECS_PER_DAY ;
2588
+ /* See TSROUND comment in interval_mul(). */
2589
+ result -> time = span -> time /factor + TSROUND (sec_remainder );
2582
2590
#endif
2583
2591
2584
2592
PG_RETURN_INTERVAL_P (result );