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