Use strtof() and not strtod() for float4 input.
authorAndrew Gierth <rhodiumtoad@postgresql.org>
Wed, 13 Feb 2019 15:19:44 +0000 (15:19 +0000)
committerAndrew Gierth <rhodiumtoad@postgresql.org>
Wed, 13 Feb 2019 15:19:44 +0000 (15:19 +0000)
Using strtod() creates a double-rounding problem; the input decimal
value is first rounded to the nearest double; rounding that to the
nearest float may then give an incorrect result.

An example is that 7.038531e-26 when input via strtod and then rounded
to float4 gives 0xAE43FEp-107 instead of the correct 0xAE43FDp-107.

Values output by earlier PG versions with extra_float_digits=3 should
all be read in with the same values as previously. However, values
supplied by other software using shortest representations could be
mis-read.

On platforms that lack a strtof() entirely, we fall back to the old
incorrect rounding behavior. (As strtof() is required by C99, such
platforms are considered of primarily historical interest.) On VS2013,
some workarounds are used to get correct error handling.

The regression tests now test for the correct input values, so
platforms that lack strtof() will need resultmap entries. An entry for
HP-UX 10 is included (more may be needed).

Reviewed-By: Tom Lane
Discussion: https://postgr.es/m/871s5emitx.fsf@news-spur.riddles.org.uk
Discussion: https://postgr.es/m/87d0owlqpv.fsf@news-spur.riddles.org.uk

13 files changed:
configure
configure.in
src/backend/utils/adt/float.c
src/include/pg_config.h.in
src/include/pg_config.h.win32
src/include/port.h
src/include/port/win32_port.h
src/port/strtof.c [new file with mode: 0644]
src/test/regress/expected/float4-misrounded-input.out [new file with mode: 0644]
src/test/regress/expected/float4.out
src/test/regress/resultmap
src/test/regress/sql/float4.sql
src/tools/msvc/Mkvcbuild.pm

index 5ef947ce656a679e6cef8174d785bd79aa84df18..0bc4a69b04abb050dbef03cd95bf4342e7114c3c 100755 (executable)
--- 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
index 96118b5e464221831a3c66a9757fc6a2d3aa0996..5960a3c2b28e80786fab94bd3e538641a6c46bc3 100644 (file)
@@ -1705,6 +1705,7 @@ AC_REPLACE_FUNCS(m4_normalize([
        strlcat
        strlcpy
        strnlen
+       strtof
 ]))
 
 case $host_os in
index 117ded8d1debcca2bba7e6de9a4efc0dbe0fe1ec..eb111ee2dbfb3264ee6f416d82d1739734ef0bd6 100644 (file)
@@ -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);
 }
 
 /*
index 82547f321f3b1788cad6d976c950e13bf4562af1..b38b0ae189982a4cadb11a4d8403cc85648f05da 100644 (file)
 /* 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
 
index a3c44f0fd80b20863c288f9520064ddbc65c3054..160fa1279e4815ff2c2a0006844c2800ad0a2b84 100644 (file)
    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
index 485e771f99fb2d73a571957ccfeb02cf58f10445..436bc83fe3803f3b6d0bb505ae5abb3911003cba 100644 (file)
@@ -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
index fdd8b2d3b15189cbec837eb97a1dc6eebddb359d..9bab75d5da0c64d1f7dd69c175190c4c910edc46 100644 (file)
@@ -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 (file)
index 0000000..7aca8e2
--- /dev/null
@@ -0,0 +1,123 @@
+/*-------------------------------------------------------------------------
+ *
+ * strtof.c
+ *
+ * Portions Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *       src/port/strtof.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "c.h"
+
+#include <float.h>
+#include <math.h>
+
+#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 (file)
index 0000000..9898d92
--- /dev/null
@@ -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)
+
index 2f47e1c202a9d6f3369d225434240f6bb501fab8..9b8b4d4280dc9d9f1ac97dfd70b44d35dae6d0db 100644 (file)
@@ -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)
+
index 46ca5639c233975694ab3d721f1e79a868da809e..3bd1585a35518e9811510646ef18ea23909f7c81 100644 (file)
@@ -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
index 46a9166d1319aba0e3a6c613f9e6d297921e5082..1fc482e146d2620df4cf53c2c8c99e9ff99a9cf7 100644 (file)
@@ -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);
index 56192f1b20c41452a3608918f5b83147b1653ce6..c930eafe8902ed9fd3ebce7989e500034d08f246 100644 (file)
@@ -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');