Fix integer-overflow problems in interval comparison.
When using integer timestamps, the interval-comparison functions tried
to compute the overall magnitude of an interval as an int64 number of
microseconds. As reported by Frazer McLean, this overflows for intervals
exceeding about 296000 years, which is bad since we nominally allow
intervals many times larger than that. That results in wrong comparison
results, and possibly in corrupted btree indexes for columns containing
such large interval values.
To fix, compute the magnitude as int128 instead. Although some compilers
have native support for int128 calculations, many don't, so create our
own support functions that can do 128-bit addition and multiplication
if the compiler support isn't there. These support functions are designed
with an eye to allowing the int128 code paths in numeric.c to be rewritten
for use on all platforms, although this patch doesn't do that, or even
provide all the int128 primitives that will be needed for it.
Back-patch as far as 9.4. Earlier releases did not guard against overflow
of interval values at all (commit 146604ec4 fixed that), so it seems not
very exciting to worry about overly-large intervals for them.
Before 9.6, we did not assume that unreferenced "static inline" functions
would not draw compiler warnings, so omit functions not directly referenced
by timestamp.c, the only present consumer of int128.h. (We could have
omitted these functions in HEAD too, but since they were written and
debugged on the way to the present patch, and they look likely to be needed
by numeric.c, let's keep them in HEAD.) I did not bother to try to prevent
such warnings in a --disable-integer-datetimes build, though.
Before 9.5, configure will never define HAVE_INT128, so the part of
int128.h that exploits a native int128 implementation is dead code in the
9.4 branch. I didn't bother to remove it, thinking that keeping the file
looking similar in different branches is more useful.
In HEAD only, add a simple test harness for int128.h in src/tools/.
In back branches, this does not change the float-timestamps code path.
That's not subject to the same kind of overflow risk, since it computes
the interval magnitude as float8. (No doubt, when this code was originally
written, overflow was disregarded for exactly that reason.) There is a
precision hazard instead :-(, but we'll avert our eyes from that question,
since no complaints have been reported and that code's deprecated anyway.
Kyotaro Horiguchi and Tom Lane
Discussion: https://postgr.es/m/1490104629.422698.918452336.26FA96B7@webmail.messagingengine.com
2017-04-06 05:51:27 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* testint128.c
|
|
|
|
* Testbed for roll-our-own 128-bit integer arithmetic.
|
|
|
|
*
|
|
|
|
* This is a standalone test program that compares the behavior of an
|
|
|
|
* implementation in int128.h to an (assumed correct) int128 native type.
|
|
|
|
*
|
|
|
|
* Copyright (c) 2017, PostgreSQL Global Development Group
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
|
|
|
* src/tools/testint128.c
|
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "postgres_fe.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* By default, we test the non-native implementation in int128.h; but
|
|
|
|
* by predefining USE_NATIVE_INT128 to 1, you can test the native
|
|
|
|
* implementation, just to be sure.
|
|
|
|
*/
|
|
|
|
#ifndef USE_NATIVE_INT128
|
|
|
|
#define USE_NATIVE_INT128 0
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "common/int128.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We assume the parts of this union are laid out compatibly.
|
|
|
|
*/
|
|
|
|
typedef union
|
|
|
|
{
|
|
|
|
int128 i128;
|
|
|
|
INT128 I128;
|
|
|
|
union
|
|
|
|
{
|
|
|
|
#ifdef WORDS_BIGENDIAN
|
|
|
|
int64 hi;
|
|
|
|
uint64 lo;
|
|
|
|
#else
|
|
|
|
uint64 lo;
|
|
|
|
int64 hi;
|
|
|
|
#endif
|
|
|
|
} hl;
|
2017-06-21 20:39:04 +02:00
|
|
|
} test128;
|
Fix integer-overflow problems in interval comparison.
When using integer timestamps, the interval-comparison functions tried
to compute the overall magnitude of an interval as an int64 number of
microseconds. As reported by Frazer McLean, this overflows for intervals
exceeding about 296000 years, which is bad since we nominally allow
intervals many times larger than that. That results in wrong comparison
results, and possibly in corrupted btree indexes for columns containing
such large interval values.
To fix, compute the magnitude as int128 instead. Although some compilers
have native support for int128 calculations, many don't, so create our
own support functions that can do 128-bit addition and multiplication
if the compiler support isn't there. These support functions are designed
with an eye to allowing the int128 code paths in numeric.c to be rewritten
for use on all platforms, although this patch doesn't do that, or even
provide all the int128 primitives that will be needed for it.
Back-patch as far as 9.4. Earlier releases did not guard against overflow
of interval values at all (commit 146604ec4 fixed that), so it seems not
very exciting to worry about overly-large intervals for them.
Before 9.6, we did not assume that unreferenced "static inline" functions
would not draw compiler warnings, so omit functions not directly referenced
by timestamp.c, the only present consumer of int128.h. (We could have
omitted these functions in HEAD too, but since they were written and
debugged on the way to the present patch, and they look likely to be needed
by numeric.c, let's keep them in HEAD.) I did not bother to try to prevent
such warnings in a --disable-integer-datetimes build, though.
Before 9.5, configure will never define HAVE_INT128, so the part of
int128.h that exploits a native int128 implementation is dead code in the
9.4 branch. I didn't bother to remove it, thinking that keeping the file
looking similar in different branches is more useful.
In HEAD only, add a simple test harness for int128.h in src/tools/.
In back branches, this does not change the float-timestamps code path.
That's not subject to the same kind of overflow risk, since it computes
the interval magnitude as float8. (No doubt, when this code was originally
written, overflow was disregarded for exactly that reason.) There is a
precision hazard instead :-(, but we'll avert our eyes from that question,
since no complaints have been reported and that code's deprecated anyway.
Kyotaro Horiguchi and Tom Lane
Discussion: https://postgr.es/m/1490104629.422698.918452336.26FA96B7@webmail.messagingengine.com
2017-04-06 05:51:27 +02:00
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Control version of comparator.
|
|
|
|
*/
|
|
|
|
static inline int
|
|
|
|
my_int128_compare(int128 x, int128 y)
|
|
|
|
{
|
|
|
|
if (x < y)
|
|
|
|
return -1;
|
|
|
|
if (x > y)
|
|
|
|
return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get a random uint64 value.
|
|
|
|
* We don't assume random() is good for more than 16 bits.
|
|
|
|
*/
|
|
|
|
static uint64
|
|
|
|
get_random_uint64(void)
|
|
|
|
{
|
|
|
|
uint64 x;
|
|
|
|
|
|
|
|
x = (uint64) (random() & 0xFFFF) << 48;
|
|
|
|
x |= (uint64) (random() & 0xFFFF) << 32;
|
|
|
|
x |= (uint64) (random() & 0xFFFF) << 16;
|
|
|
|
x |= (uint64) (random() & 0xFFFF);
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Main program.
|
|
|
|
*
|
|
|
|
* Generates a lot of random numbers and tests the implementation for each.
|
|
|
|
* The results should be reproducible, since we don't call srandom().
|
|
|
|
*
|
|
|
|
* You can give a loop count if you don't like the default 1B iterations.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
main(int argc, char **argv)
|
|
|
|
{
|
|
|
|
long count;
|
|
|
|
|
|
|
|
if (argc >= 2)
|
|
|
|
count = strtol(argv[1], NULL, 0);
|
|
|
|
else
|
|
|
|
count = 1000000000;
|
|
|
|
|
|
|
|
while (count-- > 0)
|
|
|
|
{
|
|
|
|
int64 x = get_random_uint64();
|
|
|
|
int64 y = get_random_uint64();
|
|
|
|
int64 z = get_random_uint64();
|
|
|
|
test128 t1;
|
|
|
|
test128 t2;
|
|
|
|
|
|
|
|
/* check unsigned addition */
|
|
|
|
t1.hl.hi = x;
|
|
|
|
t1.hl.lo = y;
|
|
|
|
t2 = t1;
|
|
|
|
t1.i128 += (int128) (uint64) z;
|
|
|
|
int128_add_uint64(&t2.I128, (uint64) z);
|
|
|
|
|
|
|
|
if (t1.hl.hi != t2.hl.hi || t1.hl.lo != t2.hl.lo)
|
|
|
|
{
|
|
|
|
printf("%016lX%016lX + unsigned %lX\n", x, y, z);
|
|
|
|
printf("native = %016lX%016lX\n", t1.hl.hi, t1.hl.lo);
|
|
|
|
printf("result = %016lX%016lX\n", t2.hl.hi, t2.hl.lo);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check signed addition */
|
|
|
|
t1.hl.hi = x;
|
|
|
|
t1.hl.lo = y;
|
|
|
|
t2 = t1;
|
|
|
|
t1.i128 += (int128) z;
|
|
|
|
int128_add_int64(&t2.I128, z);
|
|
|
|
|
|
|
|
if (t1.hl.hi != t2.hl.hi || t1.hl.lo != t2.hl.lo)
|
|
|
|
{
|
|
|
|
printf("%016lX%016lX + signed %lX\n", x, y, z);
|
|
|
|
printf("native = %016lX%016lX\n", t1.hl.hi, t1.hl.lo);
|
|
|
|
printf("result = %016lX%016lX\n", t2.hl.hi, t2.hl.lo);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check multiplication */
|
2017-06-21 20:39:04 +02:00
|
|
|
t1.i128 = (int128) x * (int128) y;
|
Fix integer-overflow problems in interval comparison.
When using integer timestamps, the interval-comparison functions tried
to compute the overall magnitude of an interval as an int64 number of
microseconds. As reported by Frazer McLean, this overflows for intervals
exceeding about 296000 years, which is bad since we nominally allow
intervals many times larger than that. That results in wrong comparison
results, and possibly in corrupted btree indexes for columns containing
such large interval values.
To fix, compute the magnitude as int128 instead. Although some compilers
have native support for int128 calculations, many don't, so create our
own support functions that can do 128-bit addition and multiplication
if the compiler support isn't there. These support functions are designed
with an eye to allowing the int128 code paths in numeric.c to be rewritten
for use on all platforms, although this patch doesn't do that, or even
provide all the int128 primitives that will be needed for it.
Back-patch as far as 9.4. Earlier releases did not guard against overflow
of interval values at all (commit 146604ec4 fixed that), so it seems not
very exciting to worry about overly-large intervals for them.
Before 9.6, we did not assume that unreferenced "static inline" functions
would not draw compiler warnings, so omit functions not directly referenced
by timestamp.c, the only present consumer of int128.h. (We could have
omitted these functions in HEAD too, but since they were written and
debugged on the way to the present patch, and they look likely to be needed
by numeric.c, let's keep them in HEAD.) I did not bother to try to prevent
such warnings in a --disable-integer-datetimes build, though.
Before 9.5, configure will never define HAVE_INT128, so the part of
int128.h that exploits a native int128 implementation is dead code in the
9.4 branch. I didn't bother to remove it, thinking that keeping the file
looking similar in different branches is more useful.
In HEAD only, add a simple test harness for int128.h in src/tools/.
In back branches, this does not change the float-timestamps code path.
That's not subject to the same kind of overflow risk, since it computes
the interval magnitude as float8. (No doubt, when this code was originally
written, overflow was disregarded for exactly that reason.) There is a
precision hazard instead :-(, but we'll avert our eyes from that question,
since no complaints have been reported and that code's deprecated anyway.
Kyotaro Horiguchi and Tom Lane
Discussion: https://postgr.es/m/1490104629.422698.918452336.26FA96B7@webmail.messagingengine.com
2017-04-06 05:51:27 +02:00
|
|
|
|
|
|
|
t2.hl.hi = t2.hl.lo = 0;
|
|
|
|
int128_add_int64_mul_int64(&t2.I128, x, y);
|
|
|
|
|
|
|
|
if (t1.hl.hi != t2.hl.hi || t1.hl.lo != t2.hl.lo)
|
|
|
|
{
|
|
|
|
printf("%lX * %lX\n", x, y);
|
|
|
|
printf("native = %016lX%016lX\n", t1.hl.hi, t1.hl.lo);
|
|
|
|
printf("result = %016lX%016lX\n", t2.hl.hi, t2.hl.lo);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check comparison */
|
|
|
|
t1.hl.hi = x;
|
|
|
|
t1.hl.lo = y;
|
|
|
|
t2.hl.hi = z;
|
|
|
|
t2.hl.lo = get_random_uint64();
|
|
|
|
|
|
|
|
if (my_int128_compare(t1.i128, t2.i128) !=
|
|
|
|
int128_compare(t1.I128, t2.I128))
|
|
|
|
{
|
|
|
|
printf("comparison failure: %d vs %d\n",
|
|
|
|
my_int128_compare(t1.i128, t2.i128),
|
|
|
|
int128_compare(t1.I128, t2.I128));
|
|
|
|
printf("arg1 = %016lX%016lX\n", t1.hl.hi, t1.hl.lo);
|
|
|
|
printf("arg2 = %016lX%016lX\n", t2.hl.hi, t2.hl.lo);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check case with identical hi parts; above will hardly ever hit it */
|
|
|
|
t2.hl.hi = x;
|
|
|
|
|
|
|
|
if (my_int128_compare(t1.i128, t2.i128) !=
|
|
|
|
int128_compare(t1.I128, t2.I128))
|
|
|
|
{
|
|
|
|
printf("comparison failure: %d vs %d\n",
|
|
|
|
my_int128_compare(t1.i128, t2.i128),
|
|
|
|
int128_compare(t1.I128, t2.I128));
|
|
|
|
printf("arg1 = %016lX%016lX\n", t1.hl.hi, t1.hl.lo);
|
|
|
|
printf("arg2 = %016lX%016lX\n", t2.hl.hi, t2.hl.lo);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|