summaryrefslogtreecommitdiff
path: root/src/port/strtof.c
blob: 9e37633bda5629d83656b6ce7c0fc9099c47ef06 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/*-------------------------------------------------------------------------
 *
 * 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_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