diff --git a/configure b/configure index 5ef947ce65..0bc4a69b04 100755 --- a/configure +++ b/configure @@ -15805,6 +15805,19 @@ esac fi +ac_fn_c_check_func "$LINENO" "strtof" "ac_cv_func_strtof" +if test "x$ac_cv_func_strtof" = xyes; then : + $as_echo "#define HAVE_STRTOF 1" >>confdefs.h + +else + case " $LIBOBJS " in + *" strtof.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS strtof.$ac_objext" + ;; +esac + +fi + case $host_os in diff --git a/configure.in b/configure.in index 96118b5e46..5960a3c2b2 100644 --- a/configure.in +++ b/configure.in @@ -1705,6 +1705,7 @@ AC_REPLACE_FUNCS(m4_normalize([ strlcat strlcpy strnlen + strtof ])) case $host_os in diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index 117ded8d1d..eb111ee2db 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -104,13 +104,39 @@ is_infinite(double val) /* * float4in - converts "num" to float4 + * + * Note that this code now uses strtof(), where it used to use strtod(). + * + * The motivation for using strtof() is to avoid a double-rounding problem: + * for certain decimal inputs, if you round the input correctly to a double, + * and then round the double to a float, the result is incorrect in that it + * does not match the result of rounding the decimal value to float directly. + * + * One of the best examples is 7.038531e-26: + * + * 0xAE43FDp-107 = 7.03853069185120912085...e-26 + * midpoint 7.03853100000000022281...e-26 + * 0xAE43FEp-107 = 7.03853130814879132477...e-26 + * + * making 0xAE43FDp-107 the correct float result, but if you do the conversion + * via a double, you get + * + * 0xAE43FD.7FFFFFF8p-107 = 7.03853099999999907487...e-26 + * midpoint 7.03853099999999964884...e-26 + * 0xAE43FD.80000000p-107 = 7.03853100000000022281...e-26 + * 0xAE43FD.80000008p-107 = 7.03853100000000137076...e-26 + * + * so the value rounds to the double exactly on the midpoint between the two + * nearest floats, and then rounding again to a float gives the incorrect + * result of 0xAE43FEp-107. + * */ Datum float4in(PG_FUNCTION_ARGS) { char *num = PG_GETARG_CSTRING(0); char *orig_num; - double val; + float val; char *endptr; /* @@ -135,7 +161,7 @@ float4in(PG_FUNCTION_ARGS) "real", orig_num))); errno = 0; - val = strtod(num, &endptr); + val = strtof(num, &endptr); /* did we not see anything that looks like a double? */ if (endptr == num || errno != 0) @@ -143,14 +169,14 @@ float4in(PG_FUNCTION_ARGS) int save_errno = errno; /* - * C99 requires that strtod() accept NaN, [+-]Infinity, and [+-]Inf, + * C99 requires that strtof() accept NaN, [+-]Infinity, and [+-]Inf, * but not all platforms support all of these (and some accept them * but set ERANGE anyway...) Therefore, we check for these inputs - * ourselves if strtod() fails. + * ourselves if strtof() fails. * * Note: C99 also requires hexadecimal input as well as some extended * forms of NaN, but we consider these forms unportable and don't try - * to support them. You can use 'em if your strtod() takes 'em. + * to support them. You can use 'em if your strtof() takes 'em. */ if (pg_strncasecmp(num, "NaN", 3) == 0) { @@ -195,8 +221,18 @@ float4in(PG_FUNCTION_ARGS) * precision). We'd prefer not to throw error for that, so try to * detect whether it's a "real" out-of-range condition by checking * to see if the result is zero or huge. + * + * Use isinf() rather than HUGE_VALF on VS2013 because it generates + * a spurious overflow warning for -HUGE_VALF. Also use isinf() if + * HUGE_VALF is missing. */ - if (val == 0.0 || val >= HUGE_VAL || val <= -HUGE_VAL) + if (val == 0.0 || +#if !defined(HUGE_VALF) || (defined(_MSC_VER) && (_MSC_VER < 1900)) + isinf(val) +#else + (val >= HUGE_VALF || val <= -HUGE_VALF) +#endif + ) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("\"%s\" is out of range for type real", @@ -232,13 +268,7 @@ float4in(PG_FUNCTION_ARGS) errmsg("invalid input syntax for type %s: \"%s\"", "real", orig_num))); - /* - * if we get here, we have a legal double, still need to check to see if - * it's a legal float4 - */ - check_float4_val((float4) val, isinf(val), val == 0); - - PG_RETURN_FLOAT4((float4) val); + PG_RETURN_FLOAT4(val); } /* diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 82547f321f..b38b0ae189 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -555,6 +555,9 @@ /* Define to 1 if you have the `strsignal' function. */ #undef HAVE_STRSIGNAL +/* Define to 1 if you have the `strtof' function. */ +#undef HAVE_STRTOF + /* Define to 1 if you have the `strtoll' function. */ #undef HAVE_STRTOLL diff --git a/src/include/pg_config.h.win32 b/src/include/pg_config.h.win32 index a3c44f0fd8..160fa1279e 100644 --- a/src/include/pg_config.h.win32 +++ b/src/include/pg_config.h.win32 @@ -139,6 +139,9 @@ don't. */ #define HAVE_DECL_STRNLEN 1 +/* Define to 1 if you have the `strtof' function. */ +#define HAVE_STRTOF 1 + /* Define to 1 if you have the declaration of `strtoll', and to 0 if you don't. */ #define HAVE_DECL_STRTOLL 1 diff --git a/src/include/port.h b/src/include/port.h index 485e771f99..436bc83fe3 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -381,6 +381,10 @@ extern int isinf(double x); #endif /* __clang__ && !__cplusplus */ #endif /* !HAVE_ISINF */ +#ifndef HAVE_STRTOF +extern float strtof(const char *nptr, char **endptr); +#endif + #ifndef HAVE_MKDTEMP extern char *mkdtemp(char *path); #endif diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h index fdd8b2d3b1..9bab75d5da 100644 --- a/src/include/port/win32_port.h +++ b/src/include/port/win32_port.h @@ -510,6 +510,18 @@ typedef unsigned short mode_t; #define isnan(x) _isnan(x) #endif +#if defined(_MSC_VER) && (_MSC_VER < 1900) +/* + * VS2013 has a strtof() that seems to give correct answers for valid input, + * even on the rounding edge cases, but which doesn't handle out-of-range + * input correctly. Work around that. + */ +#define HAVE_BUGGY_WINDOWS_STRTOF 1 +extern float pg_strtof(const char *nptr, char **endptr); +#define strtof(a,b) (pg_strtof((a),(b))) + +#endif + /* Pulled from Makefile.port in MinGW */ #define DLSUFFIX ".dll" diff --git a/src/port/strtof.c b/src/port/strtof.c new file mode 100644 index 0000000000..7aca8e2e3d --- /dev/null +++ b/src/port/strtof.c @@ -0,0 +1,123 @@ +/*------------------------------------------------------------------------- + * + * strtof.c + * + * Portions Copyright (c) 2019, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/port/strtof.c + * + *------------------------------------------------------------------------- + */ + +#include "c.h" + +#include +#include + +#ifndef HAVE_STRTOF +/* + * strtof() is part of C99; this version is only for the benefit of obsolete + * platforms. As such, it is known to return incorrect values for edge cases, + * which have to be allowed for in variant files for regression test results + * for any such platform. + */ + +float +strtof(const char *nptr, char **endptr) +{ + int caller_errno = errno; + double dresult; + float fresult; + + errno = 0; + dresult = strtod(nptr, endptr); + fresult = (float) dresult; + + if (errno == 0) + { + /* + * Value might be in-range for double but not float. + */ + if (dresult != 0 && fresult == 0) + caller_errno = ERANGE; /* underflow */ + if (!isinf(dresult) && isinf(fresult)) + caller_errno = ERANGE; /* overflow */ + } + else + caller_errno = errno; + + errno = caller_errno; + return fresult; +} + +#elif HAVE_BUGGY_WINDOWS_STRTOF +/* + * On Windows, there's a slightly different problem: VS2013 has a strtof() + * that returns the correct results for valid input, but may fail to report an + * error for underflow or overflow, returning 0 instead. Work around that by + * trying strtod() when strtof() returns 0.0 or [+-]Inf, and calling it an + * error if the result differs. Also, strtof() doesn't handle subnormal input + * well, so prefer to round the strtod() result in such cases. (Normally we'd + * just say "too bad" if strtof() doesn't support subnormals, but since we're + * already in here fixing stuff, we might as well do the best fix we can.) + */ +float +pg_strtof(const char *nptr, char **endptr) +{ + int caller_errno = errno; + float fresult; + + errno = 0; + fresult = (strtof)(nptr, endptr); + if (errno) + { + /* On error, just return the error to the caller. */ + return fresult; + } + else if ((*endptr == nptr) || isnan(fresult) || + ((fresult >= FLT_MIN || fresult <= -FLT_MIN) && !isinf(fresult))) + { + /* + * If we got nothing parseable, or if we got a non-0 non-subnormal + * finite value (or NaN) without error, then return that to the caller + * without error. + */ + errno = caller_errno; + return fresult; + } + else + { + /* + * Try again. errno is already 0 here. + */ + double dresult = strtod(nptr, NULL); + if (errno) + { + /* On error, just return the error */ + return fresult; + } + else if ((dresult == 0.0 && fresult == 0.0) || + (isinf(dresult) && isinf(fresult) && (fresult == dresult))) + { + /* both values are 0 or infinities of the same sign */ + errno = caller_errno; + return fresult; + } + else if ((dresult > 0 && dresult <= FLT_MIN && (float)dresult != 0.0) || + (dresult < 0 && dresult >= -FLT_MIN && (float)dresult != 0.0)) + { + /* subnormal but nonzero value */ + errno = caller_errno; + return (float) dresult; + } + else + { + errno = ERANGE; + return fresult; + } + } +} + +#endif diff --git a/src/test/regress/expected/float4-misrounded-input.out b/src/test/regress/expected/float4-misrounded-input.out new file mode 100644 index 0000000000..9898d92928 --- /dev/null +++ b/src/test/regress/expected/float4-misrounded-input.out @@ -0,0 +1,436 @@ +-- +-- FLOAT4 +-- +CREATE TABLE FLOAT4_TBL (f1 float4); +INSERT INTO FLOAT4_TBL(f1) VALUES (' 0.0'); +INSERT INTO FLOAT4_TBL(f1) VALUES ('1004.30 '); +INSERT INTO FLOAT4_TBL(f1) VALUES (' -34.84 '); +INSERT INTO FLOAT4_TBL(f1) VALUES ('1.2345678901234e+20'); +INSERT INTO FLOAT4_TBL(f1) VALUES ('1.2345678901234e-20'); +-- test for over and under flow +INSERT INTO FLOAT4_TBL(f1) VALUES ('10e70'); +ERROR: "10e70" is out of range for type real +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e70'); + ^ +INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e70'); +ERROR: "-10e70" is out of range for type real +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e70'); + ^ +INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-70'); +ERROR: "10e-70" is out of range for type real +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-70'); + ^ +INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-70'); +ERROR: "-10e-70" is out of range for type real +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-70'); + ^ +INSERT INTO FLOAT4_TBL(f1) VALUES ('10e400'); +ERROR: "10e400" is out of range for type real +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e400'); + ^ +INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e400'); +ERROR: "-10e400" is out of range for type real +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e400'); + ^ +INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-400'); +ERROR: "10e-400" is out of range for type real +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-400'); + ^ +INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-400'); +ERROR: "-10e-400" is out of range for type real +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-400'); + ^ +-- bad input +INSERT INTO FLOAT4_TBL(f1) VALUES (''); +ERROR: invalid input syntax for type real: "" +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES (''); + ^ +INSERT INTO FLOAT4_TBL(f1) VALUES (' '); +ERROR: invalid input syntax for type real: " " +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES (' '); + ^ +INSERT INTO FLOAT4_TBL(f1) VALUES ('xyz'); +ERROR: invalid input syntax for type real: "xyz" +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('xyz'); + ^ +INSERT INTO FLOAT4_TBL(f1) VALUES ('5.0.0'); +ERROR: invalid input syntax for type real: "5.0.0" +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('5.0.0'); + ^ +INSERT INTO FLOAT4_TBL(f1) VALUES ('5 . 0'); +ERROR: invalid input syntax for type real: "5 . 0" +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('5 . 0'); + ^ +INSERT INTO FLOAT4_TBL(f1) VALUES ('5. 0'); +ERROR: invalid input syntax for type real: "5. 0" +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('5. 0'); + ^ +INSERT INTO FLOAT4_TBL(f1) VALUES (' - 3.0'); +ERROR: invalid input syntax for type real: " - 3.0" +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES (' - 3.0'); + ^ +INSERT INTO FLOAT4_TBL(f1) VALUES ('123 5'); +ERROR: invalid input syntax for type real: "123 5" +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('123 5'); + ^ +-- special inputs +SELECT 'NaN'::float4; + float4 +-------- + NaN +(1 row) + +SELECT 'nan'::float4; + float4 +-------- + NaN +(1 row) + +SELECT ' NAN '::float4; + float4 +-------- + NaN +(1 row) + +SELECT 'infinity'::float4; + float4 +---------- + Infinity +(1 row) + +SELECT ' -INFINiTY '::float4; + float4 +----------- + -Infinity +(1 row) + +-- bad special inputs +SELECT 'N A N'::float4; +ERROR: invalid input syntax for type real: "N A N" +LINE 1: SELECT 'N A N'::float4; + ^ +SELECT 'NaN x'::float4; +ERROR: invalid input syntax for type real: "NaN x" +LINE 1: SELECT 'NaN x'::float4; + ^ +SELECT ' INFINITY x'::float4; +ERROR: invalid input syntax for type real: " INFINITY x" +LINE 1: SELECT ' INFINITY x'::float4; + ^ +SELECT 'Infinity'::float4 + 100.0; + ?column? +---------- + Infinity +(1 row) + +SELECT 'Infinity'::float4 / 'Infinity'::float4; + ?column? +---------- + NaN +(1 row) + +SELECT 'nan'::float4 / 'nan'::float4; + ?column? +---------- + NaN +(1 row) + +SELECT 'nan'::numeric::float4; + float4 +-------- + NaN +(1 row) + +SELECT '' AS five, * FROM FLOAT4_TBL; + five | f1 +------+------------- + | 0 + | 1004.3 + | -34.84 + | 1.23457e+20 + | 1.23457e-20 +(5 rows) + +SELECT '' AS four, f.* FROM FLOAT4_TBL f WHERE f.f1 <> '1004.3'; + four | f1 +------+------------- + | 0 + | -34.84 + | 1.23457e+20 + | 1.23457e-20 +(4 rows) + +SELECT '' AS one, f.* FROM FLOAT4_TBL f WHERE f.f1 = '1004.3'; + one | f1 +-----+-------- + | 1004.3 +(1 row) + +SELECT '' AS three, f.* FROM FLOAT4_TBL f WHERE '1004.3' > f.f1; + three | f1 +-------+------------- + | 0 + | -34.84 + | 1.23457e-20 +(3 rows) + +SELECT '' AS three, f.* FROM FLOAT4_TBL f WHERE f.f1 < '1004.3'; + three | f1 +-------+------------- + | 0 + | -34.84 + | 1.23457e-20 +(3 rows) + +SELECT '' AS four, f.* FROM FLOAT4_TBL f WHERE '1004.3' >= f.f1; + four | f1 +------+------------- + | 0 + | 1004.3 + | -34.84 + | 1.23457e-20 +(4 rows) + +SELECT '' AS four, f.* FROM FLOAT4_TBL f WHERE f.f1 <= '1004.3'; + four | f1 +------+------------- + | 0 + | 1004.3 + | -34.84 + | 1.23457e-20 +(4 rows) + +SELECT '' AS three, f.f1, f.f1 * '-10' AS x FROM FLOAT4_TBL f + WHERE f.f1 > '0.0'; + three | f1 | x +-------+-------------+-------------- + | 1004.3 | -10043 + | 1.23457e+20 | -1.23457e+21 + | 1.23457e-20 | -1.23457e-19 +(3 rows) + +SELECT '' AS three, f.f1, f.f1 + '-10' AS x FROM FLOAT4_TBL f + WHERE f.f1 > '0.0'; + three | f1 | x +-------+-------------+------------- + | 1004.3 | 994.3 + | 1.23457e+20 | 1.23457e+20 + | 1.23457e-20 | -10 +(3 rows) + +SELECT '' AS three, f.f1, f.f1 / '-10' AS x FROM FLOAT4_TBL f + WHERE f.f1 > '0.0'; + three | f1 | x +-------+-------------+-------------- + | 1004.3 | -100.43 + | 1.23457e+20 | -1.23457e+19 + | 1.23457e-20 | -1.23457e-21 +(3 rows) + +SELECT '' AS three, f.f1, f.f1 - '-10' AS x FROM FLOAT4_TBL f + WHERE f.f1 > '0.0'; + three | f1 | x +-------+-------------+------------- + | 1004.3 | 1014.3 + | 1.23457e+20 | 1.23457e+20 + | 1.23457e-20 | 10 +(3 rows) + +-- test divide by zero +SELECT '' AS bad, f.f1 / '0.0' from FLOAT4_TBL f; +ERROR: division by zero +SELECT '' AS five, * FROM FLOAT4_TBL; + five | f1 +------+------------- + | 0 + | 1004.3 + | -34.84 + | 1.23457e+20 + | 1.23457e-20 +(5 rows) + +-- test the unary float4abs operator +SELECT '' AS five, f.f1, @f.f1 AS abs_f1 FROM FLOAT4_TBL f; + five | f1 | abs_f1 +------+-------------+------------- + | 0 | 0 + | 1004.3 | 1004.3 + | -34.84 | 34.84 + | 1.23457e+20 | 1.23457e+20 + | 1.23457e-20 | 1.23457e-20 +(5 rows) + +UPDATE FLOAT4_TBL + SET f1 = FLOAT4_TBL.f1 * '-1' + WHERE FLOAT4_TBL.f1 > '0.0'; +SELECT '' AS five, * FROM FLOAT4_TBL; + five | f1 +------+-------------- + | 0 + | -34.84 + | -1004.3 + | -1.23457e+20 + | -1.23457e-20 +(5 rows) + +-- test edge-case coercions to integer +SELECT '32767.4'::float4::int2; + int2 +------- + 32767 +(1 row) + +SELECT '32767.6'::float4::int2; +ERROR: smallint out of range +SELECT '-32768.4'::float4::int2; + int2 +-------- + -32768 +(1 row) + +SELECT '-32768.6'::float4::int2; +ERROR: smallint out of range +SELECT '2147483520'::float4::int4; + int4 +------------ + 2147483520 +(1 row) + +SELECT '2147483647'::float4::int4; +ERROR: integer out of range +SELECT '-2147483648.5'::float4::int4; + int4 +------------- + -2147483648 +(1 row) + +SELECT '-2147483900'::float4::int4; +ERROR: integer out of range +SELECT '9223369837831520256'::float4::int8; + int8 +--------------------- + 9223369837831520256 +(1 row) + +SELECT '9223372036854775807'::float4::int8; +ERROR: bigint out of range +SELECT '-9223372036854775808.5'::float4::int8; + int8 +---------------------- + -9223372036854775808 +(1 row) + +SELECT '-9223380000000000000'::float4::int8; +ERROR: bigint out of range +-- Test for correct input rounding in edge cases. +-- These lists are from Paxson 1991, excluding subnormals and +-- inputs of over 9 sig. digits. +SELECT float4send('5e-20'::float4); + float4send +------------ + \x1f6c1e4a +(1 row) + +SELECT float4send('67e14'::float4); + float4send +------------ + \x59be6cea +(1 row) + +SELECT float4send('985e15'::float4); + float4send +------------ + \x5d5ab6c4 +(1 row) + +SELECT float4send('55895e-16'::float4); + float4send +------------ + \x2cc4a9bd +(1 row) + +SELECT float4send('7038531e-32'::float4); + float4send +------------ + \x15ae43fe +(1 row) + +SELECT float4send('702990899e-20'::float4); + float4send +------------ + \x2cf757ca +(1 row) + +SELECT float4send('3e-23'::float4); + float4send +------------ + \x1a111234 +(1 row) + +SELECT float4send('57e18'::float4); + float4send +------------ + \x6045c22c +(1 row) + +SELECT float4send('789e-35'::float4); + float4send +------------ + \x0a23de70 +(1 row) + +SELECT float4send('2539e-18'::float4); + float4send +------------ + \x2736f449 +(1 row) + +SELECT float4send('76173e28'::float4); + float4send +------------ + \x7616398a +(1 row) + +SELECT float4send('887745e-11'::float4); + float4send +------------ + \x3714f05c +(1 row) + +SELECT float4send('5382571e-37'::float4); + float4send +------------ + \x0d2eaca7 +(1 row) + +SELECT float4send('82381273e-35'::float4); + float4send +------------ + \x128289d0 +(1 row) + +SELECT float4send('750486563e-38'::float4); + float4send +------------ + \x0f18377e +(1 row) + +-- Test that the smallest possible normalized input value inputs +-- correctly, either in 9-significant-digit or shortest-decimal +-- format. +-- +-- exact val is 1.1754943508... +-- shortest val is 1.1754944000 +-- midpoint to next val is 1.1754944208... +SELECT float4send('1.17549435e-38'::float4); + float4send +------------ + \x00800000 +(1 row) + +SELECT float4send('1.1754944e-38'::float4); + float4send +------------ + \x00800000 +(1 row) + diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out index 2f47e1c202..9b8b4d4280 100644 --- a/src/test/regress/expected/float4.out +++ b/src/test/regress/expected/float4.out @@ -9,21 +9,37 @@ INSERT INTO FLOAT4_TBL(f1) VALUES ('1.2345678901234e+20'); INSERT INTO FLOAT4_TBL(f1) VALUES ('1.2345678901234e-20'); -- test for over and under flow INSERT INTO FLOAT4_TBL(f1) VALUES ('10e70'); -ERROR: value out of range: overflow +ERROR: "10e70" is out of range for type real LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e70'); ^ INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e70'); -ERROR: value out of range: overflow +ERROR: "-10e70" is out of range for type real LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e70'); ^ INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-70'); -ERROR: value out of range: underflow +ERROR: "10e-70" is out of range for type real LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-70'); ^ INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-70'); -ERROR: value out of range: underflow +ERROR: "-10e-70" is out of range for type real LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-70'); ^ +INSERT INTO FLOAT4_TBL(f1) VALUES ('10e400'); +ERROR: "10e400" is out of range for type real +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e400'); + ^ +INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e400'); +ERROR: "-10e400" is out of range for type real +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e400'); + ^ +INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-400'); +ERROR: "10e-400" is out of range for type real +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-400'); + ^ +INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-400'); +ERROR: "-10e-400" is out of range for type real +LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-400'); + ^ -- bad input INSERT INTO FLOAT4_TBL(f1) VALUES (''); ERROR: invalid input syntax for type real: "" @@ -306,3 +322,115 @@ SELECT '-9223372036854775808.5'::float4::int8; SELECT '-9223380000000000000'::float4::int8; ERROR: bigint out of range +-- Test for correct input rounding in edge cases. +-- These lists are from Paxson 1991, excluding subnormals and +-- inputs of over 9 sig. digits. +SELECT float4send('5e-20'::float4); + float4send +------------ + \x1f6c1e4a +(1 row) + +SELECT float4send('67e14'::float4); + float4send +------------ + \x59be6cea +(1 row) + +SELECT float4send('985e15'::float4); + float4send +------------ + \x5d5ab6c4 +(1 row) + +SELECT float4send('55895e-16'::float4); + float4send +------------ + \x2cc4a9bd +(1 row) + +SELECT float4send('7038531e-32'::float4); + float4send +------------ + \x15ae43fd +(1 row) + +SELECT float4send('702990899e-20'::float4); + float4send +------------ + \x2cf757ca +(1 row) + +SELECT float4send('3e-23'::float4); + float4send +------------ + \x1a111234 +(1 row) + +SELECT float4send('57e18'::float4); + float4send +------------ + \x6045c22c +(1 row) + +SELECT float4send('789e-35'::float4); + float4send +------------ + \x0a23de70 +(1 row) + +SELECT float4send('2539e-18'::float4); + float4send +------------ + \x2736f449 +(1 row) + +SELECT float4send('76173e28'::float4); + float4send +------------ + \x7616398a +(1 row) + +SELECT float4send('887745e-11'::float4); + float4send +------------ + \x3714f05c +(1 row) + +SELECT float4send('5382571e-37'::float4); + float4send +------------ + \x0d2eaca7 +(1 row) + +SELECT float4send('82381273e-35'::float4); + float4send +------------ + \x128289d1 +(1 row) + +SELECT float4send('750486563e-38'::float4); + float4send +------------ + \x0f18377e +(1 row) + +-- Test that the smallest possible normalized input value inputs +-- correctly, either in 9-significant-digit or shortest-decimal +-- format. +-- +-- exact val is 1.1754943508... +-- shortest val is 1.1754944000 +-- midpoint to next val is 1.1754944208... +SELECT float4send('1.17549435e-38'::float4); + float4send +------------ + \x00800000 +(1 row) + +SELECT float4send('1.1754944e-38'::float4); + float4send +------------ + \x00800000 +(1 row) + diff --git a/src/test/regress/resultmap b/src/test/regress/resultmap index 46ca5639c2..3bd1585a35 100644 --- a/src/test/regress/resultmap +++ b/src/test/regress/resultmap @@ -3,3 +3,4 @@ float8:out:i.86-.*-openbsd=float8-small-is-zero.out float8:out:i.86-.*-netbsd=float8-small-is-zero.out float8:out:m68k-.*-netbsd=float8-small-is-zero.out float8:out:i.86-pc-cygwin=float8-small-is-zero.out +float4:out:hppa.*-hp-hpux10.*=float4-misrounded-input.out diff --git a/src/test/regress/sql/float4.sql b/src/test/regress/sql/float4.sql index 46a9166d13..1fc482e146 100644 --- a/src/test/regress/sql/float4.sql +++ b/src/test/regress/sql/float4.sql @@ -16,6 +16,11 @@ INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e70'); INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-70'); INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-70'); +INSERT INTO FLOAT4_TBL(f1) VALUES ('10e400'); +INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e400'); +INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-400'); +INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-400'); + -- bad input INSERT INTO FLOAT4_TBL(f1) VALUES (''); INSERT INTO FLOAT4_TBL(f1) VALUES (' '); @@ -95,3 +100,35 @@ SELECT '9223369837831520256'::float4::int8; SELECT '9223372036854775807'::float4::int8; SELECT '-9223372036854775808.5'::float4::int8; SELECT '-9223380000000000000'::float4::int8; + +-- Test for correct input rounding in edge cases. +-- These lists are from Paxson 1991, excluding subnormals and +-- inputs of over 9 sig. digits. + +SELECT float4send('5e-20'::float4); +SELECT float4send('67e14'::float4); +SELECT float4send('985e15'::float4); +SELECT float4send('55895e-16'::float4); +SELECT float4send('7038531e-32'::float4); +SELECT float4send('702990899e-20'::float4); + +SELECT float4send('3e-23'::float4); +SELECT float4send('57e18'::float4); +SELECT float4send('789e-35'::float4); +SELECT float4send('2539e-18'::float4); +SELECT float4send('76173e28'::float4); +SELECT float4send('887745e-11'::float4); +SELECT float4send('5382571e-37'::float4); +SELECT float4send('82381273e-35'::float4); +SELECT float4send('750486563e-38'::float4); + +-- Test that the smallest possible normalized input value inputs +-- correctly, either in 9-significant-digit or shortest-decimal +-- format. +-- +-- exact val is 1.1754943508... +-- shortest val is 1.1754944000 +-- midpoint to next val is 1.1754944208... + +SELECT float4send('1.17549435e-38'::float4); +SELECT float4send('1.1754944e-38'::float4); diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 56192f1b20..c930eafe89 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -105,6 +105,8 @@ sub mkvcbuild push(@pgportfiles, 'rint.c') if ($vsVersion < '12.00'); + push(@pgportfiles, 'strtof.c') if ($vsVersion < '14.00'); + if ($vsVersion >= '9.00') { push(@pgportfiles, 'pg_crc32c_sse42_choose.c');