summaryrefslogtreecommitdiffstats
path: root/src/port/strtof.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/port/strtof.c')
-rw-r--r--src/port/strtof.c132
1 files changed, 132 insertions, 0 deletions
diff --git a/src/port/strtof.c b/src/port/strtof.c
new file mode 100644
index 0000000..92bddfb
--- /dev/null
+++ b/src/port/strtof.c
@@ -0,0 +1,132 @@
+/*-------------------------------------------------------------------------
+ *
+ * strtof.c
+ *
+ * Portions Copyright (c) 2019-2021, 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_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.)
+ *
+ * Cygwin has a strtof() which is literally just (float)strtod(), which means
+ * we can't avoid the double-rounding problem; but using this wrapper does get
+ * us proper over/underflow checks. (Also, if they fix their strtof(), the
+ * wrapper doesn't break anything.)
+ *
+ * Test results on Mingw suggest that it has the same problem, though looking
+ * at the code I can't figure out why.
+ */
+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