Skip to content

Commit

Permalink
sql: improve interval math for div and mul
Browse files Browse the repository at this point in the history
In #37582 we added correct `interval * float` semantics. Here we extend that to
float division.

In addition, change int division to be a wrapper around float division so that
remainders are correctly handled. Int multiply has no need to change since it's
lossless.

Release note (sql change): Correct interval math when multiplying or dividing
by floats or ints.
  • Loading branch information
maddyblue committed May 22, 2019
1 parent 51a5dc9 commit 567a5fe
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 8 deletions.
28 changes: 28 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/datetime
Original file line number Diff line number Diff line change
Expand Up @@ -1350,3 +1350,31 @@ SELECT '-239852040018-04-28':::DATE

statement error out of range
SELECT(7133080445639580613::INT8 + '1977-11-03'::DATE) = '-239852040018-04-28':::DATE

subtest interval_math

query TTTTTTT
SELECT
i,
i / 2::INT8,
i * 2::INT8,
i / 2::FLOAT8,
i * 2::FLOAT8,
i / .2362::FLOAT8,
i * .2362::FLOAT8
FROM
(
VALUES
('1 day'::INTERVAL),
('1 month'::INTERVAL),
('1 hour'::INTERVAL),
('1 month 2 days 4 hours'::INTERVAL)
)
AS v (i)
ORDER BY
i
----
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
11 changes: 7 additions & 4 deletions pkg/util/duration/duration.go
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ func (d Duration) Mul(x int64) Duration {

// Div returns a Duration representing a time length of d/x.
func (d Duration) Div(x int64) Duration {
return MakeDuration(d.nanos/x, d.Days/x, d.Months/x)
return d.DivFloat(float64(x))
}

// MulFloat returns a Duration representing a time length of d*x.
Expand All @@ -497,10 +497,13 @@ 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 * daysInMonth))

return MakeDuration(
int64(float64(d.nanos)/x),
int64(float64(d.Days)/x),
int64(float64(d.Months)/x),
int64((float64(d.nanos)/x)+(dayFrac*float64(nanosInDay))),
int64(dayInt),
int64(monthInt),
)
}

Expand Down
23 changes: 19 additions & 4 deletions pkg/util/duration/duration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,50 +344,65 @@ func TestAddMicros(t *testing.T) {
}
}

func TestMulFloat(t *testing.T) {
func TestFloatMath(t *testing.T) {
const nanosInMinute = nanosInSecond * 60
const nanosInHour = nanosInMinute * 60

tests := []struct {
d Duration
f float64
res Duration
mul Duration
div Duration
}{
{
Duration{Months: 1, Days: 2, nanos: nanosInHour * 2},
0.15,
Duration{Days: 4, nanos: nanosInHour*19 + nanosInMinute*30},
Duration{Months: 6, Days: 33, nanos: nanosInHour*21 + nanosInMinute*20},
},
{
Duration{Months: 1, Days: 2, nanos: nanosInHour * 2},
0.3,
Duration{Days: 9, nanos: nanosInHour * 15},
Duration{Months: 3, Days: 16, nanos: nanosInHour*22 + nanosInMinute*40},
},
{
Duration{Months: 1, Days: 2, nanos: nanosInHour * 2},
0.5,
Duration{Days: 16, nanos: nanosInHour * 1},
Duration{Months: 2, Days: 4, nanos: nanosInHour * 4},
},
{
Duration{Months: 1, Days: 2, nanos: nanosInHour * 2},
0.8,
Duration{Days: 25, nanos: nanosInHour * 16},
Duration{Months: 1, Days: 10, nanos: nanosInHour*2 + nanosInMinute*30},
},
{
Duration{Months: 1, Days: 17, nanos: nanosInHour * 2},
2.0,
Duration{Months: 2, Days: 34, nanos: nanosInHour * 4},
Duration{Days: 23, nanos: nanosInHour * 13},
},
}

for i, test := range tests {
if res := test.d.MulFloat(test.f); test.res != res {
if res := test.d.MulFloat(test.f); test.mul != res {
t.Errorf(
"%d: expected %v.MulFloat(%f) = %v, found %v",
i,
test.d,
test.f,
test.res,
test.mul,
res)
}
if res := test.d.DivFloat(test.f); test.div != res {
t.Errorf(
"%d: expected %v.DivFloat(%f) = %v, found %v",
i,
test.d,
test.f,
test.div,
res)
}
}
Expand Down

0 comments on commit 567a5fe

Please sign in to comment.