1 /*-------------------------------------------------------------------------
2 *
3 * strtof.c
4 *
5 * Portions Copyright (c) 2019-2021, PostgreSQL Global Development Group
6 *
7 *
8 * IDENTIFICATION
9 * src/port/strtof.c
10 *
11 *-------------------------------------------------------------------------
12 */
13
14 #include "c.h"
15
16 #include <float.h>
17 #include <math.h>
18
19 #ifndef HAVE_STRTOF
20 /*
21 * strtof() is part of C99; this version is only for the benefit of obsolete
22 * platforms. As such, it is known to return incorrect values for edge cases,
23 * which have to be allowed for in variant files for regression test results
24 * for any such platform.
25 */
26
27 float
strtof(const char * nptr,char ** endptr)28 strtof(const char *nptr, char **endptr)
29 {
30 int caller_errno = errno;
31 double dresult;
32 float fresult;
33
34 errno = 0;
35 dresult = strtod(nptr, endptr);
36 fresult = (float) dresult;
37
38 if (errno == 0)
39 {
40 /*
41 * Value might be in-range for double but not float.
42 */
43 if (dresult != 0 && fresult == 0)
44 caller_errno = ERANGE; /* underflow */
45 if (!isinf(dresult) && isinf(fresult))
46 caller_errno = ERANGE; /* overflow */
47 }
48 else
49 caller_errno = errno;
50
51 errno = caller_errno;
52 return fresult;
53 }
54
55 #elif HAVE_BUGGY_STRTOF
56 /*
57 * On Windows, there's a slightly different problem: VS2013 has a strtof()
58 * that returns the correct results for valid input, but may fail to report an
59 * error for underflow or overflow, returning 0 instead. Work around that by
60 * trying strtod() when strtof() returns 0.0 or [+-]Inf, and calling it an
61 * error if the result differs. Also, strtof() doesn't handle subnormal input
62 * well, so prefer to round the strtod() result in such cases. (Normally we'd
63 * just say "too bad" if strtof() doesn't support subnormals, but since we're
64 * already in here fixing stuff, we might as well do the best fix we can.)
65 *
66 * Cygwin has a strtof() which is literally just (float)strtod(), which means
67 * we can't avoid the double-rounding problem; but using this wrapper does get
68 * us proper over/underflow checks. (Also, if they fix their strtof(), the
69 * wrapper doesn't break anything.)
70 *
71 * Test results on Mingw suggest that it has the same problem, though looking
72 * at the code I can't figure out why.
73 */
74 float
pg_strtof(const char * nptr,char ** endptr)75 pg_strtof(const char *nptr, char **endptr)
76 {
77 int caller_errno = errno;
78 float fresult;
79
80 errno = 0;
81 fresult = (strtof) (nptr, endptr);
82 if (errno)
83 {
84 /* On error, just return the error to the caller. */
85 return fresult;
86 }
87 else if ((*endptr == nptr) || isnan(fresult) ||
88 ((fresult >= FLT_MIN || fresult <= -FLT_MIN) && !isinf(fresult)))
89 {
90 /*
91 * If we got nothing parseable, or if we got a non-0 non-subnormal
92 * finite value (or NaN) without error, then return that to the caller
93 * without error.
94 */
95 errno = caller_errno;
96 return fresult;
97 }
98 else
99 {
100 /*
101 * Try again. errno is already 0 here.
102 */
103 double dresult = strtod(nptr, NULL);
104
105 if (errno)
106 {
107 /* On error, just return the error */
108 return fresult;
109 }
110 else if ((dresult == 0.0 && fresult == 0.0) ||
111 (isinf(dresult) && isinf(fresult) && (fresult == dresult)))
112 {
113 /* both values are 0 or infinities of the same sign */
114 errno = caller_errno;
115 return fresult;
116 }
117 else if ((dresult > 0 && dresult <= FLT_MIN && (float) dresult != 0.0) ||
118 (dresult < 0 && dresult >= -FLT_MIN && (float) dresult != 0.0))
119 {
120 /* subnormal but nonzero value */
121 errno = caller_errno;
122 return (float) dresult;
123 }
124 else
125 {
126 errno = ERANGE;
127 return fresult;
128 }
129 }
130 }
131
132 #endif
133