Skip to content

Commit

Permalink
Merge #66345
Browse files Browse the repository at this point in the history
66345: util: Fix interval division with float rounds differently from PostgreSQL r=otan a=TszKitLo40

Before this commit, the nanos will be rounded in `MakeDuration`.
This commit round down nanos before `rounded` is called in the div of duration.

Fixes #66118

Release note (bug fix): Fixed a bug with PostgreSQL compatibility where
dividing an interval by a number would round to the nearest Microsecond
instead of always rounding down.


Co-authored-by: Zijie Lu <[email protected]>
  • Loading branch information
craig[bot] and TszKitLo40 committed Jun 16, 2021
2 parents ec36bc6 + 48d3d98 commit a021651
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 7 deletions.
4 changes: 2 additions & 2 deletions pkg/sql/logictest/testdata/logic_test/datetime
Original file line number Diff line number Diff line change
Expand Up @@ -1522,8 +1522,8 @@ ORDER BY
----
01:00:00 00:30:00 02:00:00 00:30:00 02:00:00 04:14:01.320914 00:14:10.32
1 day 12:00:00 2 days 12:00:00 2 days 4 days 05:36:31.701948 05:40:07.68
1 mon 15 days 2 mons 15 days 2 mons 4 mons 7 days 00:15:51.058425 7 days 02:03:50.4
1 mon 2 days 04:00:00 16 days 02:00:00 2 mons 4 days 08:00:00 16 days 02:00:00 2 mons 4 days 08:00:00 4 mons 15 days 28:24:59.745978 7 days 14:20:47.04
1 mon 15 days 2 mons 15 days 2 mons 4 mons 7 days 00:15:51.0912 7 days 02:03:50.4
1 mon 2 days 04:00:00 16 days 02:00:00 2 mons 4 days 08:00:00 16 days 02:00:00 2 mons 4 days 08:00:00 4 mons 15 days 28:24:59.778753 7 days 14:20:47.04

subtest tz_utc_normalization

Expand Down
25 changes: 25 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/interval
Original file line number Diff line number Diff line change
Expand Up @@ -367,3 +367,28 @@ subtest regression_62369

query error "10000000000000000000000000000000000": value out of range
SELECT INTERVAL '10000000000000000000000000000000000 year'

query T
SELECT i / 2 FROM ( VALUES
('0 days 0.253000 seconds'::interval),
(INTERVAL '0.000001'::interval),
(INTERVAL '0.000002'::interval),
(INTERVAL '0.000003'::interval),
(INTERVAL '0.000004'::interval),
(INTERVAL '0.000005'::interval),
(INTERVAL '0.000006'::interval),
(INTERVAL '0.000007'::interval),
(INTERVAL '0.000008'::interval),
(INTERVAL '0.000009'::interval)
) regression_66118(i)
----
00:00:00.1265
00:00:00
00:00:00.000001
00:00:00.000002
00:00:00.000002
00:00:00.000002
00:00:00.000003
00:00:00.000004
00:00:00.000004
00:00:00.000004
33 changes: 28 additions & 5 deletions pkg/util/duration/duration.go
Original file line number Diff line number Diff line change
Expand Up @@ -617,16 +617,39 @@ func (d Duration) MulFloat(x float64) Duration {

// DivFloat returns a Duration representing a time length of d/x.
func (d Duration) DivFloat(x float64) Duration {
monthInt, monthFrac := math.Modf(float64(d.Months) / x)
dayInt, dayFrac := math.Modf((float64(d.Days) / x) + (monthFrac * DaysPerMonth))
// In order to keep it compatible with PostgreSQL, we use the same logic.
// Refer to https://github.com/postgres/postgres/blob/e56bce5d43789cce95d099554ae9593ada92b3b7/src/backend/utils/adt/timestamp.c#L3266-L3304.
month := int32(float64(d.Months) / x)
day := int32(float64(d.Days) / x)

remainderDays := (float64(d.Months)/x - float64(month)) * DaysPerMonth
remainderDays = secRoundToEven(remainderDays)
secRemainder := (float64(d.Days)/x - float64(day) +
remainderDays - float64(int64(remainderDays))) * SecsPerDay
secRemainder = secRoundToEven(secRemainder)
if math.Abs(secRemainder) >= SecsPerDay {
day += int32(secRemainder / SecsPerDay)
secRemainder -= float64(int32(secRemainder/SecsPerDay) * SecsPerDay)
}
day += int32(remainderDays)
microSecs := float64(time.Duration(d.nanos).Microseconds())/x + secRemainder*MicrosPerMilli*MillisPerSec
retNanos := time.Duration(int64(math.RoundToEven(microSecs))) * time.Microsecond

return MakeDuration(
int64((float64(d.nanos)/x)+(dayFrac*float64(nanosInDay))),
int64(dayInt),
int64(monthInt),
retNanos.Nanoseconds(),
int64(day),
int64(month),
)
}

// secRoundToEven rounds the given float to the nearest second,
// assuming the input float is a microsecond representation of
// time
// This maps to the TSROUND macro in Postgres.
func secRoundToEven(f float64) float64 {
return math.RoundToEven(f*MicrosPerMilli*MillisPerSec) / (MicrosPerMilli * MillisPerSec)
}

// normalized returns a new Duration transformed using the equivalence rules.
// Each quantity of days greater than the threshold is moved into months,
// likewise for nanos. Integer overflow is avoided by partial transformation.
Expand Down
12 changes: 12 additions & 0 deletions pkg/util/duration/duration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,18 @@ func TestFloatMath(t *testing.T) {
Duration{Months: 2, Days: 34, nanos: nanosInHour * 4},
Duration{Days: 23, nanos: nanosInHour * 13},
},
{
Duration{Months: 0, Days: 0, nanos: nanosInSecond * 0.253000},
3.2,
Duration{Months: 0, Days: 0, nanos: nanosInSecond * 0.8096},
Duration{Months: 0, Days: 0, nanos: nanosInSecond * 0.079062},
},
{
Duration{Months: 0, Days: 0, nanos: nanosInSecond * 0.000001},
2.0,
Duration{Months: 0, Days: 0, nanos: nanosInSecond * 0.000002},
Duration{Months: 0, Days: 0, nanos: nanosInSecond * 0},
},
}

for i, test := range tests {
Expand Down

0 comments on commit a021651

Please sign in to comment.