draft patch for strtof()

Started by Andrew Gierthalmost 7 years ago12 messages
#1Andrew Gierth
andrew@tao11.riddles.org.uk
1 attachment(s)

As discussed in the Ryu thread, herewith a draft of a patch to use
strtof() for float4 input (rather than using strtod() with its
double-rounding issue).

An exhaustive search shows that this does not change the resulting
bit-pattern for any input string that could have been generated by PG
with extra_float_digits=3 set. The risk is that values generated by
other software, especially code that uses shortest-exact float output
(as a number of languages seem to do, and which PG will do if the Ryu
patch goes in) will be incorrectly input; though it appears that only
one value (7.038531e-26) is both a possible shortest-exact
representation and a rounding error (though a number of other values
round incorrectly, they are not shortest representations).

This includes a fallback to use strtod() the old way if the platform
lacks strtof(). A variant file for the new regression tests is needed
for such platforms; I've taken a stab at setting this up for the one
platform we know will need it (if there are others, the buildfarm will
let us know in due course).

--
Andrew (irc:RhodiumToad)

Attachments:

strtof.patchtext/x-patchDownload
diff --git a/configure b/configure
index 06fc3c6835..e3176e24e9 100755
--- a/configure
+++ b/configure
@@ -15802,6 +15802,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 4efb912c4d..bdaab717d7 100644
--- a/configure.in
+++ b/configure.in
@@ -1703,6 +1703,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..dce6ea31cf 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)
 		{
@@ -196,7 +222,7 @@ float4in(PG_FUNCTION_ARGS)
 			 * detect whether it's a "real" out-of-range condition by checking
 			 * to see if the result is zero or huge.
 			 */
-			if (val == 0.0 || val >= HUGE_VAL || val <= -HUGE_VAL)
+			if (val == 0.0 || val >= HUGE_VALF || val <= -HUGE_VALF)
 				ereport(ERROR,
 						(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 						 errmsg("\"%s\" is out of range for type real",
@@ -232,13 +258,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/port.h b/src/include/port.h
index a55c473262..a6950c1526 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/utils/float.h b/src/include/utils/float.h
index 0f82a25ede..5e11ab4aa9 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -17,6 +17,11 @@
 
 #include <math.h>
 
+/* this might not be needed */
+#ifndef HUGE_VALF
+#define HUGE_VALF ((float)HUGE_VAL)
+#endif
+
 #ifndef M_PI
 /* From my RH5.2 gcc math.h file - thomas 2000-04-03 */
 #define M_PI 3.14159265358979323846
diff --git a/src/port/strtof.c b/src/port/strtof.c
new file mode 100644
index 0000000000..75a41fbcfa
--- /dev/null
+++ b/src/port/strtof.c
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * strtof.c
+ *
+ * Portions Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/port/strtof.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "c.h"
+
+#include <float.h>
+#include <math.h>
+
+/*
+ * strtof() is part of C99; this file 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;
+}
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..b8795c6fdf
--- /dev/null
+++ b/src/test/regress/expected/float4-misrounded-input.out
@@ -0,0 +1,401 @@
+--
+-- 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');
+                                           ^
+-- 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)
+
diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out
index 2f47e1c202..3aa7f257d5 100644
--- a/src/test/regress/expected/float4.out
+++ b/src/test/regress/expected/float4.out
@@ -9,19 +9,19 @@ 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');
                                            ^
 -- bad input
@@ -306,3 +306,96 @@ 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)
+
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..12421f7c75 100644
--- a/src/test/regress/sql/float4.sql
+++ b/src/test/regress/sql/float4.sql
@@ -95,3 +95,24 @@ 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);
#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Gierth (#1)
Re: draft patch for strtof()

Andrew Gierth <andrew@tao11.riddles.org.uk> writes:

As discussed in the Ryu thread, herewith a draft of a patch to use
strtof() for float4 input (rather than using strtod() with its
double-rounding issue).

The errno handling in strtof seems bizarrely overcomplex; why do
you need the separate caller_errno variable?

This includes a fallback to use strtod() the old way if the platform
lacks strtof(). A variant file for the new regression tests is needed
for such platforms; I've taken a stab at setting this up for the one
platform we know will need it (if there are others, the buildfarm will
let us know in due course).

I'm not that much on board with introducing test cases that we know,
beyond question, are going to be portability headaches. What will
we actually gain with this, compared to just not having the test case?
I can't see that it's worth either the buildfarm cycles or the human
maintenance effort just to prove that, yes, some platforms have
portability corner cases. I also don't like the prospect that we
ship releases that will fail basic regression tests on platforms
we haven't tested. Coping with such failures is a large burden
for affected packagers or end users, especially when the only useful
"coping" mechanism is to ignore the regression test failure. Might
as well not have it.

regards, tom lane

#3Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Tom Lane (#2)
Re: draft patch for strtof()

"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:

As discussed in the Ryu thread, herewith a draft of a patch to use
strtof() for float4 input (rather than using strtod() with its
double-rounding issue).

Tom> The errno handling in strtof seems bizarrely overcomplex; why do
Tom> you need the separate caller_errno variable?

Eh. I was preserving the conventional behaviour of setting errno only on
errors and not resetting it to 0, but I suppose you could argue that
that is overkill given that the function is called only in one place
that's supposed to have already set errno to 0.

(And yes, I missed a couple of files and the windows build breaks,
working on those)

This includes a fallback to use strtod() the old way if the platform
lacks strtof(). A variant file for the new regression tests is needed
for such platforms; I've taken a stab at setting this up for the one
platform we know will need it (if there are others, the buildfarm will
let us know in due course).

Tom> I'm not that much on board with introducing test cases that we
Tom> know, beyond question, are going to be portability headaches. What
Tom> will we actually gain with this, compared to just not having the
Tom> test case?

The purpose of the test case is to ensure that we're getting the right
values on input. If these values fail on any platform that anyone
actually cares about, then I think we need to know about it.

--
Andrew (irc:RhodiumToad)

#4Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Gierth (#3)
Re: draft patch for strtof()

Andrew Gierth <andrew@tao11.riddles.org.uk> writes:

"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> The errno handling in strtof seems bizarrely overcomplex; why do
Tom> you need the separate caller_errno variable?

Eh. I was preserving the conventional behaviour of setting errno only on
errors and not resetting it to 0,

Oh, I see. -ENOCAFFEINE.

but I suppose you could argue that
that is overkill given that the function is called only in one place
that's supposed to have already set errno to 0.

Well, we probably oughtta endeavor to maintain compatibility with
the function's standard behavior, because other calls to it are
likely to creep in over time. Objection withdrawn.

Tom> I'm not that much on board with introducing test cases that we
Tom> know, beyond question, are going to be portability headaches. What
Tom> will we actually gain with this, compared to just not having the
Tom> test case?

The purpose of the test case is to ensure that we're getting the right
values on input. If these values fail on any platform that anyone
actually cares about, then I think we need to know about it.

Meh. I think the actual outcome will be that we define any platform
that gets the wrong answer as one that we don't care about, mainly
because we won't have any practical way to fix it. That being the
situation, trying to maintain a test case seems like pointless
make-work.

(FWIW, I'm running the patch on gaur's host, just to confirm it
does what you expect. Should have an answer in an hour or so ...)

regards, tom lane

#5Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Andrew Gierth (#1)
1 attachment(s)
Re: draft patch for strtof()

See if Windows likes this one any better.

--
Andrew (irc:RhodiumToad)

Attachments:

strtof2.patchtext/x-patchDownload
diff --git a/configure b/configure
index 06fc3c6835..e3176e24e9 100755
--- a/configure
+++ b/configure
@@ -15802,6 +15802,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 4efb912c4d..bdaab717d7 100644
--- a/configure.in
+++ b/configure.in
@@ -1703,6 +1703,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..dce6ea31cf 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)
 		{
@@ -196,7 +222,7 @@ float4in(PG_FUNCTION_ARGS)
 			 * detect whether it's a "real" out-of-range condition by checking
 			 * to see if the result is zero or huge.
 			 */
-			if (val == 0.0 || val >= HUGE_VAL || val <= -HUGE_VAL)
+			if (val == 0.0 || val >= HUGE_VALF || val <= -HUGE_VALF)
 				ereport(ERROR,
 						(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 						 errmsg("\"%s\" is out of range for type real",
@@ -232,13 +258,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/port.h b/src/include/port.h
index a55c473262..a6950c1526 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/utils/float.h b/src/include/utils/float.h
index 0f82a25ede..5e11ab4aa9 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -17,6 +17,11 @@
 
 #include <math.h>
 
+/* this might not be needed */
+#ifndef HUGE_VALF
+#define HUGE_VALF ((float)HUGE_VAL)
+#endif
+
 #ifndef M_PI
 /* From my RH5.2 gcc math.h file - thomas 2000-04-03 */
 #define M_PI 3.14159265358979323846
diff --git a/src/port/strtof.c b/src/port/strtof.c
new file mode 100644
index 0000000000..75a41fbcfa
--- /dev/null
+++ b/src/port/strtof.c
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * strtof.c
+ *
+ * Portions Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/port/strtof.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "c.h"
+
+#include <float.h>
+#include <math.h>
+
+/*
+ * strtof() is part of C99; this file 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;
+}
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..b8795c6fdf
--- /dev/null
+++ b/src/test/regress/expected/float4-misrounded-input.out
@@ -0,0 +1,401 @@
+--
+-- 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');
+                                           ^
+-- 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)
+
diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out
index 2f47e1c202..3aa7f257d5 100644
--- a/src/test/regress/expected/float4.out
+++ b/src/test/regress/expected/float4.out
@@ -9,19 +9,19 @@ 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');
                                            ^
 -- bad input
@@ -306,3 +306,96 @@ 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)
+
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..12421f7c75 100644
--- a/src/test/regress/sql/float4.sql
+++ b/src/test/regress/sql/float4.sql
@@ -95,3 +95,24 @@ 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);
#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Gierth (#5)
Re: draft patch for strtof()

Andrew Gierth <andrew@tao11.riddles.org.uk> writes:

See if Windows likes this one any better.

Doubtful, because AFAICT it's the same patch.

regards, tom lane

#7Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Tom Lane (#6)
1 attachment(s)
Re: draft patch for strtof()

"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:

Andrew Gierth <andrew@tao11.riddles.org.uk> writes:

See if Windows likes this one any better.

Tom> Doubtful, because AFAICT it's the same patch.

Sigh. copied wrong file. Let's try that again.

--
Andrew (irc:RhodiumToad)

Attachments:

strtof2.patchtext/x-patchDownload
diff --git a/configure b/configure
index 06fc3c6835..e3176e24e9 100755
--- a/configure
+++ b/configure
@@ -15802,6 +15802,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 4efb912c4d..bdaab717d7 100644
--- a/configure.in
+++ b/configure.in
@@ -1703,6 +1703,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..b907613056 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)
 		{
@@ -196,7 +222,14 @@ float4in(PG_FUNCTION_ARGS)
 			 * detect whether it's a "real" out-of-range condition by checking
 			 * to see if the result is zero or huge.
 			 */
-			if (val == 0.0 || val >= HUGE_VAL || val <= -HUGE_VAL)
+
+			if (val == 0.0 ||
+#ifdef HUGE_VALF
+				val >= HUGE_VALF || val <= -HUGE_VALF
+#else
+				isinf(val)
+#endif
+				)
 				ereport(ERROR,
 						(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 						 errmsg("\"%s\" is out of range for type real",
@@ -232,13 +265,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 9d99816eae..84ca929439 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 1a89a8c24e..793897271c 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 a55c473262..a6950c1526 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/port/strtof.c b/src/port/strtof.c
new file mode 100644
index 0000000000..75a41fbcfa
--- /dev/null
+++ b/src/port/strtof.c
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * strtof.c
+ *
+ * Portions Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/port/strtof.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "c.h"
+
+#include <float.h>
+#include <math.h>
+
+/*
+ * strtof() is part of C99; this file 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;
+}
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..b8795c6fdf
--- /dev/null
+++ b/src/test/regress/expected/float4-misrounded-input.out
@@ -0,0 +1,401 @@
+--
+-- 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');
+                                           ^
+-- 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)
+
diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out
index 2f47e1c202..3aa7f257d5 100644
--- a/src/test/regress/expected/float4.out
+++ b/src/test/regress/expected/float4.out
@@ -9,19 +9,19 @@ 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');
                                            ^
 -- bad input
@@ -306,3 +306,96 @@ 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)
+
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..12421f7c75 100644
--- a/src/test/regress/sql/float4.sql
+++ b/src/test/regress/sql/float4.sql
@@ -95,3 +95,24 @@ 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);
#8Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#4)
Re: draft patch for strtof()

I wrote:

(FWIW, I'm running the patch on gaur's host, just to confirm it
does what you expect. Should have an answer in an hour or so ...)

It does --- it compiles cleanly, and the float4 output matches
float4-misrounded-input.out.

(This is the v1 patch not v2.)

regards, tom lane

#9Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Tom Lane (#8)
Re: draft patch for strtof()

"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:

(FWIW, I'm running the patch on gaur's host, just to confirm it
does what you expect. Should have an answer in an hour or so ...)

Tom> It does --- it compiles cleanly, and the float4 output matches
Tom> float4-misrounded-input.out.

Well I'm glad _something_ works.

Because it turns out that Windows (at least the version running on
Appveyor) completely fucks this up; strtof() is apparently returning
infinity or zero _without setting errno_ for values out of range for
float: input of "10e70" returns +inf with no error, input of "10e-70"
returns (exactly) 0.0 with no error.

*facepalm*

Any windows-users have any idea about this?

--
Andrew (irc:RhodiumToad)

#10Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Andrew Gierth (#9)
Re: draft patch for strtof()

"Andrew" == Andrew Gierth <andrew@tao11.riddles.org.uk> writes:

Andrew> Because it turns out that Windows (at least the version running
Andrew> on Appveyor) completely fucks this up; strtof() is apparently
Andrew> returning infinity or zero _without setting errno_ for values
Andrew> out of range for float: input of "10e70" returns +inf with no
Andrew> error, input of "10e-70" returns (exactly) 0.0 with no error.

This bug turns out to be dependent on compiler/SDK versions, not
surprisingly. So far I have figured out how to invoke these combinations
on appveyor:

VS2013 / SDK 7.1 (as per cfbot): fails
VS2015 / SDK 8.1: works

Trying to figure out how to get other combinations to test.

--
Andrew (irc:RhodiumToad)

#11Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Andrew Gierth (#7)
1 attachment(s)
Re: draft patch for strtof()

This one builds ok on appveyor with at least three different VS
versions. Though I've not tried the exact combination of commands
used by cfbot... yet.

--
Andrew (irc:RhodiumToad)

Attachments:

strtof3.patchtext/x-patchDownload
diff --git a/configure b/configure
index 06fc3c6835..e3176e24e9 100755
--- a/configure
+++ b/configure
@@ -15802,6 +15802,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 4efb912c4d..bdaab717d7 100644
--- a/configure.in
+++ b/configure.in
@@ -1703,6 +1703,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..31f53878a2 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,17 @@ 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.
 			 */
-			if (val == 0.0 || val >= HUGE_VAL || val <= -HUGE_VAL)
+			if (val == 0.0 ||
+#if 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 +267,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 9d99816eae..84ca929439 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 1a89a8c24e..793897271c 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 a55c473262..a6950c1526 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..0d957dc663
--- /dev/null
+++ b/src/port/strtof.c
@@ -0,0 +1,112 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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.
+ */
+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) ||
+			 (fresult != 0.0 && !isinf(fresult)))
+	{
+		/*
+		 * If we got nothing parseable, or if we got a non-0 finite value (or
+		 * NaN) without error, then return that to the caller without error.
+		 */
+		errno = caller_errno;
+		return fresult;
+	}
+	else
+	{
+		/*
+		 * Try again.
+		 */
+		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
+		{
+			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..a554bd29bf
--- /dev/null
+++ b/src/test/regress/expected/float4-misrounded-input.out
@@ -0,0 +1,417 @@
+--
+-- 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)
+
diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out
index 2f47e1c202..62b29648f0 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,96 @@ 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)
+
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..a6bce9710b 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,24 @@ 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);
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');
#12Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Andrew Gierth (#11)
1 attachment(s)
Re: draft patch for strtof()

Merge back in some code changes made in the Ryu patch that really belong
here, in preparation for rebasing Ryu on top of this (since this is
really a separate functional change). Posting this mainly to let cfbot
take a look at it.

--
Andrew (irc:RhodiumToad)

Attachments:

strtof4.patchtext/x-patchDownload
diff --git a/configure b/configure
index ddb3c8b1ba..378860e9ce 100755
--- a/configure
+++ b/configure
@@ -15812,6 +15812,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 3d8888805c..08fc8fd1bf 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 <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
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');